using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;

namespace Unity.Netcode.Components
{
    /// <summary>
    /// A component for syncing transforms.
    /// NetworkTransform will read the underlying transform and replicate it to clients.
    /// The replicated value will be automatically be interpolated (if active) and applied to the underlying GameObject's transform.
    /// </summary>
    [DisallowMultipleComponent]
    [AddComponentMenu("Netcode/Network Transform")]
    [DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
    public class NetworkTransform : NetworkBehaviour
    {
        /// <summary>
        /// The default position change threshold value.
        /// Any changes above this threshold will be replicated.
        /// </summary>
        public const float PositionThresholdDefault = 0.001f;

        /// <summary>
        /// The default rotation angle change threshold value.
        /// Any changes above this threshold will be replicated.
        /// </summary>
        public const float RotAngleThresholdDefault = 0.01f;

        /// <summary>
        /// The default scale change threshold value.
        /// Any changes above this threshold will be replicated.
        /// </summary>
        public const float ScaleThresholdDefault = 0.01f;

        /// <summary>
        /// The handler delegate type that takes client requested changes and returns resulting changes handled by the server.
        /// </summary>
        /// <param name="pos">The position requested by the client.</param>
        /// <param name="rot">The rotation requested by the client.</param>
        /// <param name="scale">The scale requested by the client.</param>
        /// <returns>The resulting position, rotation and scale changes after handling.</returns>
        public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);

        /// <summary>
        /// The handler that gets invoked when server receives a change from a client.
        /// This handler would be useful for server to modify pos/rot/scale before applying client's request.
        /// </summary>
        public OnClientRequestChangeDelegate OnClientRequestChange;

        /// <summary>
        /// When set each state update will contain a state identifier
        /// </summary>
        internal static bool TrackByStateId;

        /// <summary>
        /// Enabled by default.
        /// When set (enabled by default), NetworkTransform will send common state updates using unreliable network delivery
        /// to provide a higher tolerance to poor network conditions (especially packet loss). When disabled, all state updates
        /// are sent using a reliable fragmented sequenced network delivery.
        /// </summary>
        /// <remarks>
        /// The following more critical state updates are still sent as reliable fragmented sequenced:
        /// - The initial synchronization state update
        /// - The teleporting state update.
        /// - When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in
        /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced.
        /// 
        /// In order to preserve a continual consistency of axial values when unreliable delta messaging is enabled (due to the
        /// possibility of dropping packets), NetworkTransform instances will send 1 axial frame synchronization update per
        /// second (only for the axis marked to synchronize are sent as reliable fragmented sequenced) as long as a delta state
        /// update had been previously sent. When a NetworkObject is at rest, axial frame synchronization updates are not sent.
        /// </remarks>
        [Tooltip("When set, NetworkTransform will send common state updates using unreliable network delivery " +
            "to provide a higher tolerance to poor network conditions (especially packet loss). When disabled, all state updates are " +
            "sent using reliable fragmented sequenced network delivery.")]
        public bool UseUnreliableDeltas = false;

        /// <summary>
        /// Data structure used to synchronize the <see cref="NetworkTransform"/>
        /// </summary>
        public struct NetworkTransformState : INetworkSerializable
        {
            private const int k_InLocalSpaceBit = 0x00000001; // Persists between state updates (authority dictates if this is set)
            private const int k_PositionXBit = 0x00000002;
            private const int k_PositionYBit = 0x00000004;
            private const int k_PositionZBit = 0x00000008;
            private const int k_RotAngleXBit = 0x00000010;
            private const int k_RotAngleYBit = 0x00000020;
            private const int k_RotAngleZBit = 0x00000040;
            private const int k_ScaleXBit = 0x00000080;
            private const int k_ScaleYBit = 0x00000100;
            private const int k_ScaleZBit = 0x00000200;
            private const int k_TeleportingBit = 0x00000400;
            private const int k_Interpolate = 0x00000800; // Persists between state updates (authority dictates if this is set)
            private const int k_QuaternionSync = 0x00001000; // Persists between state updates (authority dictates if this is set)
            private const int k_QuaternionCompress = 0x00002000; // Persists between state updates (authority dictates if this is set)
            private const int k_UseHalfFloats = 0x00004000; // Persists between state updates (authority dictates if this is set)
            private const int k_Synchronization = 0x00008000;
            private const int k_PositionSlerp = 0x00010000; // Persists between state updates (authority dictates if this is set)
            private const int k_IsParented = 0x00020000; // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order
            private const int k_SynchBaseHalfFloat = 0x00040000;
            private const int k_ReliableSequenced = 0x00080000;
            private const int k_UseUnreliableDeltas = 0x00100000;
            private const int k_UnreliableFrameSync = 0x00200000;
            private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier 

            // Stores persistent and state relative flags
            private uint m_Bitset;
            internal uint BitSet
            {
                get { return m_Bitset; }
                set { m_Bitset = value; }
            }

            // Used to store the tick calculated sent time
            internal double SentTime;

            // Used for full precision position updates
            internal float PositionX, PositionY, PositionZ;

            // Used for full precision Euler updates
            internal float RotAngleX, RotAngleY, RotAngleZ;

            // Used for full precision quaternion updates
            internal Quaternion Rotation;

            // Used for full precision scale updates
            internal float ScaleX, ScaleY, ScaleZ;

            // Used for half precision delta position updates
            internal Vector3 CurrentPosition;
            internal Vector3 DeltaPosition;
            internal NetworkDeltaPosition NetworkDeltaPosition;

            // Used for half precision scale
            internal HalfVector3 HalfVectorScale;
            internal Vector3 Scale;
            internal Vector3 LossyScale;

            // Used for half precision quaternion
            internal HalfVector4 HalfVectorRotation;

            // Used to store a compressed quaternion
            internal uint QuaternionCompressed;

            // Authoritative and non-authoritative sides use this to determine if a NetworkTransformState is
            // dirty or not.
            internal bool IsDirty { get; set; }

            /// <summary>
            /// The last byte size of the <see cref="NetworkTransformState"/> updated.
            /// </summary>
            public int LastSerializedSize { get; internal set; }

            // Used for NetworkDeltaPosition delta position synchronization
            internal int NetworkTick;

            // Used when tracking by state ID is enabled
            internal int StateId;

            // Set when a state has been explicitly set (i.e. SetState)
            internal bool ExplicitSet;

            // Used during serialization
            private FastBufferReader m_Reader;
            private FastBufferWriter m_Writer;

            /// <summary>
            /// When set, the <see cref="NetworkTransform"/> is operates in local space
            /// </summary>
            public bool InLocalSpace
            {
                get => GetFlag(k_InLocalSpaceBit);
                internal set
                {
                    SetFlag(value, k_InLocalSpaceBit);
                }
            }

            // Position
            /// <summary>
            /// When set, the X-Axis position value has changed
            /// </summary>
            public bool HasPositionX
            {
                get => GetFlag(k_PositionXBit);
                internal set
                {
                    SetFlag(value, k_PositionXBit);
                }
            }

            /// <summary>
            /// When set, the Y-Axis position value has changed
            /// </summary>
            public bool HasPositionY
            {
                get => GetFlag(k_PositionYBit);
                internal set
                {
                    SetFlag(value, k_PositionYBit);
                }
            }

            /// <summary>
            /// When set, the Z-Axis position value has changed
            /// </summary>
            public bool HasPositionZ
            {
                get => GetFlag(k_PositionZBit);
                internal set
                {
                    SetFlag(value, k_PositionZBit);
                }
            }

            /// <summary>
            /// When set, at least one of the position axis values has changed.
            /// </summary>
            public bool HasPositionChange
            {
                get
                {
                    return HasPositionX | HasPositionY | HasPositionZ;
                }
            }

            // RotAngles
            /// <summary>
            /// When set, the Euler rotation X-Axis value has changed.
            /// </summary>
            /// <remarks>
            /// When quaternion synchronization is enabled all axis are always updated.
            /// </remarks>
            public bool HasRotAngleX
            {
                get => GetFlag(k_RotAngleXBit);
                internal set
                {
                    SetFlag(value, k_RotAngleXBit);
                }
            }

            /// <summary>
            /// When set, the Euler rotation Y-Axis value has changed.
            /// </summary>
            /// <remarks>
            /// When quaternion synchronization is enabled all axis are always updated.
            /// </remarks>
            public bool HasRotAngleY
            {
                get => GetFlag(k_RotAngleYBit);
                internal set
                {
                    SetFlag(value, k_RotAngleYBit);
                }
            }

            /// <summary>
            /// When set, the Euler rotation Z-Axis value has changed.
            /// </summary>
            /// <remarks>
            /// When quaternion synchronization is enabled all axis are always updated.
            /// </remarks>
            public bool HasRotAngleZ
            {
                get => GetFlag(k_RotAngleZBit);
                internal set
                {
                    SetFlag(value, k_RotAngleZBit);
                }
            }

            /// <summary>
            /// When set, at least one of the rotation axis values has changed.
            /// </summary>
            /// <remarks>
            /// When quaternion synchronization is enabled all axis are always updated.
            /// </remarks>
            public bool HasRotAngleChange
            {
                get
                {
                    return HasRotAngleX | HasRotAngleY | HasRotAngleZ;
                }
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            internal bool HasScale(int axisIndex)
            {
                return GetFlag(k_ScaleXBit << axisIndex);
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            internal void SetHasScale(int axisIndex, bool isSet)
            {
                SetFlag(isSet, k_ScaleXBit << axisIndex);
            }

            // Scale
            /// <summary>
            /// When set, the X-Axis scale value has changed.
            /// </summary>
            public bool HasScaleX
            {
                get => GetFlag(k_ScaleXBit);
                internal set
                {
                    SetFlag(value, k_ScaleXBit);
                }
            }

            /// <summary>
            /// When set, the Y-Axis scale value has changed.
            /// </summary>
            public bool HasScaleY
            {
                get => GetFlag(k_ScaleYBit);
                internal set
                {
                    SetFlag(value, k_ScaleYBit);
                }
            }

            /// <summary>
            /// When set, the Z-Axis scale value has changed.
            /// </summary>
            public bool HasScaleZ
            {
                get => GetFlag(k_ScaleZBit);
                internal set
                {
                    SetFlag(value, k_ScaleZBit);
                }
            }

            /// <summary>
            /// When set, at least one of the scale axis values has changed.
            /// </summary>
            public bool HasScaleChange
            {
                get
                {
                    return HasScaleX | HasScaleY | HasScaleZ;
                }
            }

            /// <summary>
            /// When set, the current state will be treated as a teleport.
            /// </summary>
            /// <remarks>
            /// When teleporting:
            /// - Interpolation is reset.
            /// - If using half precision, full precision values are used.
            /// - All axis marked to be synchronized will be updated.
            /// </remarks>
            public bool IsTeleportingNextFrame
            {
                get => GetFlag(k_TeleportingBit);
                internal set
                {
                    SetFlag(value, k_TeleportingBit);
                }
            }

            /// <summary>
            /// When set the <see cref="NetworkTransform"/> is uses interpolation.
            /// </summary>
            /// <remarks>
            /// Authority does not apply interpolation via <see cref="NetworkTransform"/>.
            /// Authority should handle its own motion/rotation/scale smoothing locally.
            /// </remarks>
            public bool UseInterpolation
            {
                get => GetFlag(k_Interpolate);
                internal set
                {
                    SetFlag(value, k_Interpolate);
                }
            }

            /// <summary>
            /// When enabled, this <see cref="NetworkTransform"/> instance uses <see cref="Quaternion"/> synchronization.
            /// </summary>
            /// <remarks>
            /// Use quaternion synchronization if you are nesting <see cref="NetworkTransform"/>s and rotation can occur on both the parent and child.
            /// When quaternion synchronization is enabled, the entire quaternion is updated when there are any changes to any axial values.
            /// You can use half float precision or quaternion compression to reduce the bandwidth cost.
            /// </remarks>
            public bool QuaternionSync
            {
                get => GetFlag(k_QuaternionSync);
                internal set
                {
                    SetFlag(value, k_QuaternionSync);
                }
            }

            /// <summary>
            /// When set <see cref="Quaternion"/>s will be compressed down to 4 bytes using a smallest three implementation.
            /// </summary>
            /// <remarks>
            /// This only will be applied when <see cref="QuaternionSync"/> is enabled.
            /// Half float precision provides a higher precision than quaternion compression but at the cost of 4 additional bytes per update.
            /// - Quaternion Compression: 4 bytes per delta update
            /// - Half float precision: 8 bytes per delta update
            /// </remarks>
            public bool QuaternionCompression
            {
                get => GetFlag(k_QuaternionCompress);
                internal set
                {
                    SetFlag(value, k_QuaternionCompress);
                }
            }

            /// <summary>
            /// When set, the <see cref="NetworkTransform"/> will use half float precision for position, rotation, and scale.
            /// </summary>
            /// <remarks>
            /// Postion is synchronized through delta position updates in order to reduce precision loss/drift and to extend to positions beyond the limitation of half float maximum values.
            /// Rotation and scale both use half float precision (<see cref="HalfVector4"/> and <see cref="HalfVector3"/>)
            /// </remarks>
            public bool UseHalfFloatPrecision
            {
                get => GetFlag(k_UseHalfFloats);
                internal set
                {
                    SetFlag(value, k_UseHalfFloats);
                }
            }

            /// <summary>
            /// When set, this indicates it is the first state being synchronized.
            /// Typically when the associate <see cref="NetworkObject"/> is spawned or a client is being synchronized after connecting to a network session in progress.
            /// </summary>
            public bool IsSynchronizing
            {
                get => GetFlag(k_Synchronization);
                internal set
                {
                    SetFlag(value, k_Synchronization);
                }
            }

            /// <summary>
            /// Determines if position interpolation will Slerp towards its target position.
            /// This is only really useful if you are moving around a point in a circular pattern.
            /// </summary>
            public bool UsePositionSlerp
            {
                get => GetFlag(k_PositionSlerp);
                internal set
                {
                    SetFlag(value, k_PositionSlerp);
                }
            }

            /// <summary>
            /// Returns whether this state update was a frame synchronization when 
            /// UseUnreliableDeltas is enabled. When set, the entire transform will 
            /// be or has been synchronized.
            /// </summary>
            public bool IsUnreliableFrameSync()
            {
                return UnreliableFrameSync;
            }

            /// <summary>
            /// Returns true if this state was sent with reliable delivery.
            /// If false, then it was sent with unreliable delivery.
            /// </summary>
            /// <remarks>
            /// Unreliable delivery will only be used if <see cref="UseUnreliableDeltas"/> is set.
            /// </remarks>
            public bool IsReliableStateUpdate()
            {
                return ReliableSequenced;
            }

            internal bool IsParented
            {
                get => GetFlag(k_IsParented);
                set
                {
                    SetFlag(value, k_IsParented);
                }
            }

            internal bool SynchronizeBaseHalfFloat
            {
                get => GetFlag(k_SynchBaseHalfFloat);
                set
                {
                    SetFlag(value, k_SynchBaseHalfFloat);
                }
            }

            internal bool ReliableSequenced
            {
                get => GetFlag(k_ReliableSequenced);
                set
                {
                    SetFlag(value, k_ReliableSequenced);
                }
            }

            internal bool UseUnreliableDeltas
            {
                get => GetFlag(k_UseUnreliableDeltas);
                set
                {
                    SetFlag(value, k_UseUnreliableDeltas);
                }
            }

            internal bool UnreliableFrameSync
            {
                get => GetFlag(k_UnreliableFrameSync);
                set
                {
                    SetFlag(value, k_UnreliableFrameSync);
                }
            }

            internal bool TrackByStateId
            {
                get => GetFlag(k_TrackStateId);
                set
                {
                    SetFlag(value, k_TrackStateId);
                }
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            private bool GetFlag(int flag)
            {
                return (m_Bitset & flag) != 0;
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            private void SetFlag(bool set, int flag)
            {
                if (set) { m_Bitset = m_Bitset | (uint)flag; }
                else { m_Bitset = m_Bitset & (uint)~flag; }
            }

            internal void ClearBitSetForNextTick()
            {
                // Clear everything but flags that should persist between state updates until changed by authority
                m_Bitset &= k_InLocalSpaceBit | k_Interpolate | k_UseHalfFloats | k_QuaternionSync | k_QuaternionCompress | k_PositionSlerp | k_UseUnreliableDeltas;
                IsDirty = false;
            }

            /// <summary>
            /// Returns the current state's rotation. If there is no change in the rotation,
            /// then it will return <see cref="Quaternion.identity"/>.
            /// </summary>
            /// <remarks>
            /// When there is no change in an updated state's rotation then there are no values to return.
            /// Checking for <see cref="HasRotAngleChange"/> is one way to detect this.
            /// </remarks>
            /// <returns><see cref="Quaternion"/></returns>
            public Quaternion GetRotation()
            {
                if (HasRotAngleChange)
                {
                    if (QuaternionSync)
                    {
                        return Rotation;
                    }
                    else
                    {
                        return Quaternion.Euler(RotAngleX, RotAngleY, RotAngleZ);
                    }
                }
                return Quaternion.identity;
            }

            /// <summary>
            /// Returns the current state's position. If there is no change in position,
            /// then it returns <see cref="Vector3.zero"/>.
            /// </summary>
            /// <remarks>
            /// When there is no change in an updated state's position then there are no values to return.
            /// Checking for <see cref="HasPositionChange"/> is one way to detect this.
            /// When used with half precision it returns the half precision delta position state update 
            /// which will not be the full position.
            /// To get a NettworkTransform's full position, use <see cref="GetSpaceRelativePosition(bool)"/> and
            /// pass true as the parameter.
            /// </remarks>
            /// <returns><see cref="Vector3"/></returns>
            public Vector3 GetPosition()
            {
                if (HasPositionChange)
                {
                    if (UseHalfFloatPrecision)
                    {
                        if (IsTeleportingNextFrame)
                        {
                            return CurrentPosition;
                        }
                        else
                        {
                            return NetworkDeltaPosition.GetFullPosition();
                        }
                    }
                    else
                    {
                        return new Vector3(PositionX, PositionY, PositionZ);
                    }
                }
                return Vector3.zero;
            }

            /// <summary>
            /// Returns the current state's scale. If there is no change in scale,
            /// then it returns <see cref="Vector3.zero"/>.
            /// </summary>
            /// <remarks>
            /// When there is no change in an updated state's scale then there are no values to return.
            /// Checking for <see cref="HasScaleChange"/> is one way to detect this.
            /// </remarks>
            /// <returns><see cref="Vector3"/></returns>
            public Vector3 GetScale()
            {
                if (HasScaleChange)
                {
                    if (UseHalfFloatPrecision)
                    {
                        if (IsTeleportingNextFrame)
                        {
                            return Scale;
                        }
                        else
                        {
                            return HalfVectorScale.ToVector3();
                        }
                    }
                    else
                    {
                        return new Vector3(ScaleX, ScaleY, ScaleZ);
                    }
                }
                return Vector3.zero;
            }

            /// <summary>
            /// The network tick that this state was sent by the authoritative instance.
            /// </summary>
            /// <returns><see cref="int"/></returns>
            public int GetNetworkTick()
            {
                return NetworkTick;
            }

            internal HalfVector3 HalfEulerRotation;

            /// <summary>
            /// Serializes this <see cref="NetworkTransformState"/>
            /// </summary>
            public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
            {
                // Used to calculate the LastSerializedSize value
                var positionStart = 0;
                var isWriting = serializer.IsWriter;
                if (isWriting)
                {
                    m_Writer = serializer.GetFastBufferWriter();
                    positionStart = m_Writer.Position;
                }
                else
                {
                    m_Reader = serializer.GetFastBufferReader();
                    positionStart = m_Reader.Position;
                }

                // Synchronize State Flags and Network Tick
                {
                    if (isWriting)
                    {
                        if (UseUnreliableDeltas)
                        {
                            // If teleporting, synchronizing, doing an axial frame sync, or using half float precision and we collapsed a delta into the base position
                            if (IsTeleportingNextFrame || IsSynchronizing || UnreliableFrameSync || (UseHalfFloatPrecision && NetworkDeltaPosition.CollapsedDeltaIntoBase))
                            {
                                // Send the message reliably
                                ReliableSequenced = true;
                            }
                            else
                            {
                                ReliableSequenced = false;
                            }
                        }
                        else // If not using UseUnreliableDeltas, then always use reliable fragmented sequenced
                        {
                            ReliableSequenced = true;
                        }

                        BytePacker.WriteValueBitPacked(m_Writer, m_Bitset);
                        // We use network ticks as opposed to absolute time as the authoritative
                        // side updates on every new tick.
                        BytePacker.WriteValueBitPacked(m_Writer, NetworkTick);

                    }
                    else
                    {
                        ByteUnpacker.ReadValueBitPacked(m_Reader, out m_Bitset);
                        // We use network ticks as opposed to absolute time as the authoritative
                        // side updates on every new tick.
                        ByteUnpacker.ReadValueBitPacked(m_Reader, out NetworkTick);
                    }
                }

                // If debugging states and track by state identifier is enabled, serialize the current state identifier
                if (TrackByStateId)
                {
                    serializer.SerializeValue(ref StateId);
                }

                // Synchronize Position
                if (HasPositionChange)
                {
                    if (UseHalfFloatPrecision)
                    {
                        NetworkDeltaPosition.SynchronizeBase = SynchronizeBaseHalfFloat;

                        // Apply which axis should be updated for both write/read (teleporting, synchronizing, or just updating)
                        NetworkDeltaPosition.HalfVector3.AxisToSynchronize[0] = HasPositionX;
                        NetworkDeltaPosition.HalfVector3.AxisToSynchronize[1] = HasPositionY;
                        NetworkDeltaPosition.HalfVector3.AxisToSynchronize[2] = HasPositionZ;

                        if (IsTeleportingNextFrame)
                        {
                            // **Always use full precision when teleporting and UseHalfFloatPrecision is enabled**
                            serializer.SerializeValue(ref CurrentPosition);
                            // If we are synchronizing, then include the half vector position's delta offset
                            if (IsSynchronizing)
                            {
                                serializer.SerializeValue(ref DeltaPosition);
                                if (!isWriting)
                                {
                                    NetworkDeltaPosition.NetworkTick = NetworkTick;
                                    NetworkDeltaPosition.NetworkSerialize(serializer);
                                }
                                else
                                {
                                    serializer.SerializeNetworkSerializable(ref NetworkDeltaPosition);
                                }
                            }
                        }
                        else
                        {
                            if (!isWriting)
                            {
                                NetworkDeltaPosition.NetworkTick = NetworkTick;
                                NetworkDeltaPosition.NetworkSerialize(serializer);
                            }
                            else
                            {
                                serializer.SerializeNetworkSerializable(ref NetworkDeltaPosition);
                            }
                        }
                    }
                    else // Full precision axis specific position synchronization
                    {
                        if (HasPositionX)
                        {
                            serializer.SerializeValue(ref PositionX);
                        }

                        if (HasPositionY)
                        {
                            serializer.SerializeValue(ref PositionY);
                        }

                        if (HasPositionZ)
                        {
                            serializer.SerializeValue(ref PositionZ);
                        }
                    }
                }

                // Synchronize Rotation
                if (HasRotAngleChange)
                {
                    if (QuaternionSync)
                    {
                        // Always use the full quaternion if teleporting
                        if (IsTeleportingNextFrame)
                        {
                            serializer.SerializeValue(ref Rotation);
                        }
                        else
                        {
                            // Use the quaternion compressor if enabled
                            if (QuaternionCompression)
                            {
                                if (isWriting)
                                {
                                    QuaternionCompressed = QuaternionCompressor.CompressQuaternion(ref Rotation);
                                }

                                serializer.SerializeValue(ref QuaternionCompressed);

                                if (!isWriting)
                                {
                                    QuaternionCompressor.DecompressQuaternion(ref Rotation, QuaternionCompressed);
                                }
                            }
                            else
                            {
                                if (UseHalfFloatPrecision)
                                {
                                    if (isWriting)
                                    {
                                        HalfVectorRotation.UpdateFrom(ref Rotation);
                                    }

                                    serializer.SerializeNetworkSerializable(ref HalfVectorRotation);

                                    if (!isWriting)
                                    {
                                        Rotation = HalfVectorRotation.ToQuaternion();
                                    }
                                }
                                else
                                {
                                    serializer.SerializeValue(ref Rotation);
                                }
                            }
                        }
                    }
                    else // Euler Rotation Synchronization
                    {
                        // Half float precision (full precision when teleporting)
                        if (UseHalfFloatPrecision && !IsTeleportingNextFrame)
                        {
                            if (HasRotAngleChange)
                            {
                                // Apply which axis should be updated for both write/read
                                HalfEulerRotation.AxisToSynchronize[0] = HasRotAngleX;
                                HalfEulerRotation.AxisToSynchronize[1] = HasRotAngleY;
                                HalfEulerRotation.AxisToSynchronize[2] = HasRotAngleZ;

                                if (isWriting)
                                {
                                    HalfEulerRotation.Set(RotAngleX, RotAngleY, RotAngleZ);
                                }

                                serializer.SerializeValue(ref HalfEulerRotation);

                                if (!isWriting)
                                {
                                    var eulerRotation = HalfEulerRotation.ToVector3();
                                    if (HasRotAngleX)
                                    {
                                        RotAngleX = eulerRotation.x;
                                    }

                                    if (HasRotAngleY)
                                    {
                                        RotAngleY = eulerRotation.y;
                                    }

                                    if (HasRotAngleZ)
                                    {
                                        RotAngleZ = eulerRotation.z;
                                    }
                                }
                            }
                        }
                        else // Full precision Euler
                        {
                            // RotAngle Values
                            if (HasRotAngleX)
                            {
                                serializer.SerializeValue(ref RotAngleX);
                            }

                            if (HasRotAngleY)
                            {
                                serializer.SerializeValue(ref RotAngleY);
                            }

                            if (HasRotAngleZ)
                            {
                                serializer.SerializeValue(ref RotAngleZ);
                            }
                        }
                    }
                }

                // Synchronize Scale
                if (HasScaleChange)
                {
                    // If we are teleporting (which includes synchronizing) and the associated NetworkObject has a parent
                    // then we want to serialize the LossyScale since NetworkObject spawn order is not  guaranteed 
                    if (IsTeleportingNextFrame && IsParented)
                    {
                        serializer.SerializeValue(ref LossyScale);
                    }
                    // Half precision scale synchronization
                    if (UseHalfFloatPrecision)
                    {
                        if (IsTeleportingNextFrame)
                        {
                            serializer.SerializeValue(ref Scale);
                        }
                        else
                        {
                            // Apply which axis should be updated for both write/read
                            HalfVectorScale.AxisToSynchronize[0] = HasScaleX;
                            HalfVectorScale.AxisToSynchronize[1] = HasScaleY;
                            HalfVectorScale.AxisToSynchronize[2] = HasScaleZ;

                            // For scale, when half precision is enabled we can still only send the axis with deltas
                            if (isWriting)
                            {
                                HalfVectorScale.Set(Scale[0], Scale[1], Scale[2]);
                            }

                            serializer.SerializeValue(ref HalfVectorScale);

                            if (!isWriting)
                            {
                                Scale = HalfVectorScale.ToVector3();
                                if (HasScaleX)
                                {
                                    ScaleX = Scale.x;
                                }

                                if (HasScaleY)
                                {
                                    ScaleY = Scale.y;
                                }

                                if (HasScaleZ)
                                {
                                    ScaleZ = Scale.x;
                                }
                            }
                        }
                    }
                    else // Full precision scale synchronization
                    {
                        if (HasScaleX)
                        {
                            serializer.SerializeValue(ref ScaleX);
                        }

                        if (HasScaleY)
                        {
                            serializer.SerializeValue(ref ScaleY);
                        }

                        if (HasScaleZ)
                        {
                            serializer.SerializeValue(ref ScaleZ);
                        }
                    }
                }

                // Only if we are receiving state
                if (!isWriting)
                {
                    // Go ahead and mark the local state dirty
                    IsDirty = HasPositionChange || HasRotAngleChange || HasScaleChange;
                    LastSerializedSize = m_Reader.Position - positionStart;
                }
                else
                {
                    LastSerializedSize = m_Writer.Position - positionStart;
                }
            }
        }

        /// <summary>
        /// When enabled (default), the x component of position will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncPositionX = true;

        /// <summary>
        /// When enabled (default), the y component of position will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncPositionY = true;

        /// <summary>
        /// When enabled (default), the z component of position will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncPositionZ = true;

        private bool SynchronizePosition
        {
            get
            {
                return SyncPositionX || SyncPositionY || SyncPositionZ;
            }
        }

        /// <summary>
        /// When enabled (default), the x component of rotation will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// When <see cref="UseQuaternionSynchronization"/> is enabled this does not apply.
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncRotAngleX = true;

        /// <summary>
        /// When enabled (default), the y component of rotation will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// When <see cref="UseQuaternionSynchronization"/> is enabled this does not apply.
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncRotAngleY = true;

        /// <summary>
        /// When enabled (default), the z component of rotation will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// When <see cref="UseQuaternionSynchronization"/> is enabled this does not apply.
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncRotAngleZ = true;

        private bool SynchronizeRotation
        {
            get
            {
                return SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ;
            }
        }

        /// <summary>
        /// When enabled (default), the x component of scale will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncScaleX = true;

        /// <summary>
        /// When enabled (default), the y component of scale will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncScaleY = true;

        /// <summary>
        /// When enabled (default), the z component of scale will be synchronized by authority.
        /// </summary>
        /// <remarks>
        /// Changes to this on non-authoritative instances has no effect.
        /// </remarks>
        public bool SyncScaleZ = true;

        private bool SynchronizeScale
        {
            get
            {
                return SyncScaleX || SyncScaleY || SyncScaleZ;
            }
        }

        /// <summary>
        /// The position threshold value that triggers a delta state update by the authoritative instance.
        /// </summary>
        /// <remarks>
        /// Note: setting this to zero will update position every network tick whether it changed or not.
        /// </remarks>
        public float PositionThreshold = PositionThresholdDefault;

        /// <summary>
        /// The rotation threshold value that triggers a delta state update by the authoritative instance.
        /// </summary>
        /// <remarks>
        /// Minimum Value: 0.00001
        /// Maximum Value: 360.0
        /// </remarks>
        [Range(0.00001f, 360.0f)]
        public float RotAngleThreshold = RotAngleThresholdDefault;

        /// <summary>
        /// The scale threshold value that triggers a delta state update by the authoritative instance.
        /// </summary>
        /// <remarks>
        /// Note: setting this to zero will update position every network tick whether it changed or not.
        /// </remarks>
        public float ScaleThreshold = ScaleThresholdDefault;

        /// <summary>
        /// Enable this on the authority side for quaternion synchronization
        /// </summary>
        /// <remarks>
        /// This is synchronized by authority. During runtime, this should only be changed by the
        /// authoritative side. Non-authoritative instances will be overridden by the next
        /// authoritative state update.
        /// </remarks>
        [Tooltip("When enabled, this will synchronize the full Quaternion (i.e. all Euler rotation axis are updated if one axis has a delta)")]
        public bool UseQuaternionSynchronization = false;

        /// <summary>
        /// Enabled this on the authority side for quaternion compression
        /// </summary>
        /// <remarks>
        /// This has a lower precision than half float precision. Recommended only for low precision
        /// scenarios. <see cref="UseHalfFloatPrecision"/> provides better precision at roughly half
        /// the cost of a full quaternion update.
        /// This is synchronized by authority. During runtime, this should only be changed by the
        /// authoritative side. Non-authoritative instances will be overridden by the next
        /// authoritative state update.
        /// </remarks>
        [Tooltip("When enabled, this uses a smallest three implementation that reduces full Quaternion updates down to the size of an unsigned integer (ignores half float precision settings).")]
        public bool UseQuaternionCompression = false;

        /// <summary>
        /// Enable this to use half float precision for position, rotation, and scale.
        /// When enabled, delta position synchronization is used.
        /// </summary>
        /// <remarks>
        /// This is synchronized by authority. During runtime, this should only be changed by the
        /// authoritative side. Non-authoritative instances will be overridden by the next
        /// authoritative state update.
        /// </remarks>
        [Tooltip("When enabled, this will use half float precision values for position (uses delta position updating), rotation (except when Quaternion compression is enabled), and scale.")]
        public bool UseHalfFloatPrecision = false;

        /// <summary>
        /// Sets whether the transform should be treated as local (true) or world (false) space.
        /// </summary>
        /// <remarks>
        /// This is synchronized by authority. During runtime, this should only be changed by the
        /// authoritative side. Non-authoritative instances will be overridden by the next
        /// authoritative state update.
        /// </remarks>
        [Tooltip("Sets whether this transform should sync in local space or in world space")]
        public bool InLocalSpace = false;

        /// <summary>
        /// When enabled (default) interpolation is applied.
        /// When disabled interpolation is disabled.
        /// </summary>
        /// <remarks>
        /// This is synchronized by authority and changes to interpolation during runtime forces a
        /// teleport/full update. During runtime, this should only be changed by the authoritative
        /// side. Non-authoritative instances will be overridden by the next authoritative state update.
        /// </remarks>
        public bool Interpolate = true;

        /// <summary>
        /// When true and interpolation is enabled, this will Slerp to the target position.
        /// </summary>
        /// <remarks>
        /// This is synchronized by authority and only applies to position interpolation.
        /// During runtime, this should only be changed by the authoritative side. Non-authoritative
        /// instances will be overridden by the next authoritative state update.
        /// </remarks>
        [Tooltip("When enabled the position interpolator will Slerp towards its current target position.")]
        public bool SlerpPosition = false;

        /// <summary>
        /// Used to determine who can write to this transform. Server only for this transform.
        /// Changing this value alone in a child implementation will not allow you to create a NetworkTransform which can be written to by clients. See the ClientNetworkTransform Sample
        /// in the package samples for how to implement a NetworkTransform with client write support.
        /// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing
        /// </summary>
        public bool CanCommitToTransform { get; protected set; }

        /// <summary>
        /// Internally used by <see cref="NetworkTransform"/> to keep track of whether this <see cref="NetworkBehaviour"/> derived class instance
        /// was instantiated on the server side or not.
        /// </summary>
        protected bool m_CachedIsServer; // Note: we no longer use this and are only keeping it until we decide to deprecate it

        /// <summary>
        /// Internally used by <see cref="NetworkTransform"/> to keep track of the <see cref="NetworkManager"/> instance assigned to this
        /// this <see cref="NetworkBehaviour"/> derived class instance.
        /// </summary>
        protected NetworkManager m_CachedNetworkManager;

        /// <summary>
        /// Helper method that returns the space relative position of the transform.
        /// </summary>
        /// <remarks>
        /// If InLocalSpace is <see cref="true"/> then it returns the transform.localPosition
        /// If InLocalSpace is <see cref="false"/> then it returns the transform.position
        /// When invoked on the non-authority side:
        /// If <see cref="getCurrentState"/> is true then it will return the most
        /// current authority position from the most recent state update. This can be useful
        /// if interpolation is enabled and you need to determine the final target position.
        /// When invoked on the authority side:
        /// It will always return the space relative position.
        /// </remarks>
        /// <param name="getCurrentState">
        /// Authority always returns the space relative transform position (whether true or false).
        /// Non-authority:
        /// When false (default): returns the space relative transform position
        /// When true: returns the authority position from the most recent state update.
        /// </param>
        /// <returns><see cref="Vector3"/></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Vector3 GetSpaceRelativePosition(bool getCurrentState = false)
        {
            if (!getCurrentState || CanCommitToTransform)
            {
                return InLocalSpace ? transform.localPosition : transform.position;
            }
            else
            {
                // When half float precision is enabled, get the NetworkDeltaPosition's full position
                if (UseHalfFloatPrecision)
                {
                    return m_HalfPositionState.GetFullPosition();
                }
                else
                {
                    // Otherwise, just get the current position
                    return m_CurrentPosition;
                }
            }
        }

        /// <summary>
        /// Helper method that returns the space relative rotation of the transform.
        /// </summary>
        /// <remarks>
        /// If InLocalSpace is <see cref="true"/> then it returns the transform.localRotation
        /// If InLocalSpace is <see cref="false"/> then it returns the transform.rotation
        /// When invoked on the non-authority side:
        /// If <see cref="getCurrentState"/> is true then it will return the most
        /// current authority rotation from the most recent state update. This can be useful
        /// if interpolation is enabled and you need to determine the final target rotation.
        /// When invoked on the authority side:
        /// It will always return the space relative rotation.
        /// </remarks>
        /// <param name="getCurrentState">
        /// Authority always returns the space relative transform rotation (whether true or false).
        /// Non-authority:
        /// When false (default): returns the space relative transform rotation
        /// When true: returns the authority rotation from the most recent state update.
        /// </param>
        /// <returns><see cref="Quaternion"/></returns>
        public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false)
        {
            if (!getCurrentState || CanCommitToTransform)
            {
                return InLocalSpace ? transform.localRotation : transform.rotation;
            }
            else
            {
                return m_CurrentRotation;
            }
        }

        /// <summary>
        /// Helper method that returns the scale of the transform.
        /// </summary>
        /// <remarks>
        /// When invoked on the non-authority side:
        /// If <see cref="getCurrentState"/> is true then it will return the most
        /// current authority scale from the most recent state update. This can be useful
        /// if interpolation is enabled and you need to determine the final target scale.
        /// When invoked on the authority side:
        /// It will always return the space relative scale.
        /// </remarks>
        /// <param name="getCurrentState">
        /// Authority always returns the space relative transform scale (whether true or false).
        /// Non-authority:
        /// When false (default): returns the space relative transform scale
        /// When true: returns the authority scale from the most recent state update.
        /// </param>
        /// <returns><see cref="Vector3"/></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Vector3 GetScale(bool getCurrentState = false)
        {
            if (!getCurrentState || CanCommitToTransform)
            {
                return transform.localScale;
            }
            else
            {
                return m_CurrentScale;
            }
        }

        // Used by both authoritative and non-authoritative instances.
        // This represents the most recent local authoritative state.
        private NetworkTransformState m_LocalAuthoritativeNetworkState;

        internal NetworkTransformState LocalAuthoritativeNetworkState
        {
            get
            {
                return m_LocalAuthoritativeNetworkState;
            }
            set
            {
                m_LocalAuthoritativeNetworkState = value;
            }
        }

        private ClientRpcParams m_ClientRpcParams = new ClientRpcParams() { Send = new ClientRpcSendParams() };
        private List<ulong> m_ClientIds = new List<ulong>() { 0 };

        private BufferedLinearInterpolatorVector3 m_PositionInterpolator;
        private BufferedLinearInterpolatorVector3 m_ScaleInterpolator;
        private BufferedLinearInterpolatorQuaternion m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value

        // Non-Authoritative's current position, scale, and rotation that is used to assure the non-authoritative side cannot make adjustments to
        // the portions of the transform being synchronized.
        private Vector3 m_CurrentPosition;
        private Vector3 m_TargetPosition;
        private Vector3 m_CurrentScale;
        private Vector3 m_TargetScale;
        private Quaternion m_CurrentRotation;
        private Vector3 m_TargetRotation;


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal void UpdatePositionInterpolator(Vector3 position, double time, bool resetInterpolator = false)
        {
            if (!CanCommitToTransform)
            {
                if (resetInterpolator)
                {
                    m_PositionInterpolator.ResetTo(position, time);
                }
                else
                {
                    m_PositionInterpolator.AddMeasurement(position, time);
                }
            }
        }

#if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS
        /// <summary>
        /// For debugging delta position and half vector3
        /// </summary>
        protected delegate void AddLogEntryHandler(ref NetworkTransformState networkTransformState, ulong targetClient, bool preUpdate = false);
        protected AddLogEntryHandler m_AddLogEntry;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void AddLogEntry(ref NetworkTransformState networkTransformState, ulong targetClient, bool preUpdate = false)
        {
            m_AddLogEntry?.Invoke(ref networkTransformState, targetClient, preUpdate);
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected int GetStateId(ref NetworkTransformState state)
        {
            return state.StateId;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected NetworkDeltaPosition GetHalfPositionState()
        {
            return m_HalfPositionState;
        }

#else
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void AddLogEntry(ref NetworkTransformState networkTransformState, ulong targetClient, bool preUpdate = false)
        {
        }
#endif

        /// <summary>
        /// Only used when UseHalfFloatPrecision is enabled
        /// </summary>
        private NetworkDeltaPosition m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0);

        internal void UpdatePositionSlerp()
        {
            if (m_PositionInterpolator != null)
            {
                m_PositionInterpolator.IsSlerp = SlerpPosition;
            }
        }

        /// <summary>
        /// Determines if synchronization is needed.
        /// Basically only if we are running in owner authoritative mode and it
        /// is the owner being synchronized we don't want to synchronize with
        /// the exception of the NetworkObject being owned by the server.
        /// </summary>
        private bool ShouldSynchronizeHalfFloat(ulong targetClientId)
        {
            if (!IsServerAuthoritative() && NetworkObject.OwnerClientId == targetClientId)
            {
                // Return false for all client owners but return true for the server
                return NetworkObject.IsOwnedByServer;
            }
            return true;
        }

        // For test logging purposes
        internal NetworkTransformState SynchronizeState;

        /// <summary>
        /// This is invoked when a new client joins (server and client sides)
        /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState)
        /// Client Side: Adds the interpolated state which applies the NetworkTransformState as well
        /// </summary>
        /// <remarks>
        /// If a derived class overrides this, then make sure to invoke this base method!
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="serializer"></param>
        /// <param name="targetClientId">the clientId being synchronized (both reading and writing)</param>
        protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
        {
            var targetClientId = m_TargetIdBeingSynchronized;
            var synchronizationState = new NetworkTransformState()
            {
                HalfEulerRotation = new HalfVector3(),
                HalfVectorRotation = new HalfVector4(),
                HalfVectorScale = new HalfVector3(),
                NetworkDeltaPosition = new NetworkDeltaPosition(),
            };

            if (serializer.IsWriter)
            {
                synchronizationState.IsTeleportingNextFrame = true;
                var transformToCommit = transform;
                // If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for
                // for the non-authority side to be able to properly synchronize delta position updates.
                ApplyTransformToNetworkStateWithInfo(ref synchronizationState, ref transformToCommit, true, targetClientId);
                synchronizationState.NetworkSerialize(serializer);
                SynchronizeState = synchronizationState;
            }
            else
            {
                synchronizationState.NetworkSerialize(serializer);
                // Set the transform's synchronization modes
                InLocalSpace = synchronizationState.InLocalSpace;
                Interpolate = synchronizationState.UseInterpolation;
                UseQuaternionSynchronization = synchronizationState.QuaternionSync;
                UseHalfFloatPrecision = synchronizationState.UseHalfFloatPrecision;
                UseQuaternionCompression = synchronizationState.QuaternionCompression;
                SlerpPosition = synchronizationState.UsePositionSlerp;
                UpdatePositionSlerp();

                // Teleport/Fully Initialize based on the state
                ApplyTeleportingState(synchronizationState);
                SynchronizeState = synchronizationState;
                m_LocalAuthoritativeNetworkState = synchronizationState;
                m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
                m_LocalAuthoritativeNetworkState.IsSynchronizing = false;
            }
        }

        /// <summary>
        /// This will try to send/commit the current transform delta states (if any)
        /// </summary>
        /// <remarks>
        /// Only client owners or the server should invoke this method
        /// </remarks>
        /// <param name="transformToCommit">the transform to be committed</param>
        /// <param name="dirtyTime">time it was marked dirty</param>
        protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime)
        {
            if (!IsSpawned)
            {
                NetworkLog.LogError($"Cannot commit transform when not spawned!");
                return;
            }

            // Only the server or the owner is allowed to commit a transform
            if (!IsServer && !IsOwner)
            {
                var errorMessage = gameObject != NetworkObject.gameObject ?
                    $"Non-authority instance of {NetworkObject.gameObject.name} is trying to commit a transform on {gameObject.name}!" :
                    $"Non-authority instance of {NetworkObject.gameObject.name} is trying to commit a transform!";
                NetworkLog.LogError(errorMessage);
                return;
            }

            // If we are authority, update the authoritative state
            if (CanCommitToTransform)
            {
                OnUpdateAuthoritativeState(ref transformToCommit);
            }
            else // Non-Authority
            {
                var position = InLocalSpace ? transformToCommit.localPosition : transformToCommit.position;
                var rotation = InLocalSpace ? transformToCommit.localRotation : transformToCommit.rotation;
                // We are an owner requesting to update our state
                if (!IsServer)
                {
                    SetStateServerRpc(position, rotation, transformToCommit.localScale, false);
                }
                else // Server is always authoritative (including owner authoritative)
                {
                    SetStateClientRpc(position, rotation, transformToCommit.localScale, false);
                }
            }
        }

        /// <summary>
        /// Invoked just prior to being pushed to non-authority instances.
        /// </summary>
        /// <remarks>
        /// This is useful to know the exact position, rotation, or scale values sent
        /// to non-authoritative instances. This is only invoked on the authoritative
        /// instance.
        /// </remarks>
        /// <param name="networkTransformState">the state being pushed</param>
        protected virtual void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState)
        {
        }

        // Only set if a delta has been sent, this is reset after an axial synch has been sent
        // to assure the instance doesn't continue to send axial synchs when an object is at rest.
        private bool m_DeltaSynch;

        /// <summary>
        /// Authoritative side only
        /// If there are any transform delta states, this method will synchronize the
        /// state with all non-authority instances.
        /// </summary>
        private void TryCommitTransform(ref Transform transformToCommit, bool synchronize = false, bool settingState = false)
        {
            // Only the server or the owner is allowed to commit a transform
            if (!IsServer && !IsOwner)
            {
                NetworkLog.LogError($"[{name}] is trying to commit the transform without authority!");
                return;
            }

            // If the transform has deltas (returns dirty) or if an explicitly set state is pending
            if (m_LocalAuthoritativeNetworkState.ExplicitSet || ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize))
            {
                m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize;

                // If the state was explicitly set, then update the network tick to match the locally calculate tick
                if (m_LocalAuthoritativeNetworkState.ExplicitSet)
                {
                    m_LocalAuthoritativeNetworkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick;
                }

                // Send the state update
                UpdateTransformState();

                // Mark the last tick and the old state (for next ticks)
                m_OldState = m_LocalAuthoritativeNetworkState;

                // Reset the teleport and explicit state flags after we have sent the state update.
                // These could be set again in the below OnAuthorityPushTransformState virtual method
                m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
                m_LocalAuthoritativeNetworkState.ExplicitSet = false;

                try
                {
                    // Notify of the pushed state update
                    OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState);
                }
                catch (Exception ex)
                {
                    Debug.LogException(ex);
                }

                // The below is part of assuring we only send a frame synch, when sending unreliable deltas, if 
                // we have already sent at least one unreliable delta state update. At this point in the callstack,
                // a delta state update has just been sent in the above UpdateTransformState() call and as long as
                // we didn't send a frame synch and we are not synchronizing then we know at least one unreliable
                // delta has been sent. Under this scenario, we should start checking for this instance's alloted 
                // frame synch "tick slot". Once we send a frame synch, if no other deltas occur after that
                // (i.e. the object is at rest) then we will stop sending frame synch's until the object begins
                // moving, rotating, or scaling again.
                if (UseUnreliableDeltas && !m_LocalAuthoritativeNetworkState.UnreliableFrameSync && !synchronize)
                {
                    m_DeltaSynch = true;
                }
            }
        }

        /// <summary>
        /// Initializes the interpolators with the current transform values
        /// </summary>
        private void ResetInterpolatedStateToCurrentAuthoritativeState()
        {
            var serverTime = NetworkManager.ServerTime.Time;

            UpdatePositionInterpolator(GetSpaceRelativePosition(), serverTime, true);
            UpdatePositionSlerp();

            m_ScaleInterpolator.ResetTo(transform.localScale, serverTime);
            m_RotationInterpolator.ResetTo(GetSpaceRelativeRotation(), serverTime);
        }

        /// <summary>
        /// Used for integration testing:
        /// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed dirty information returned
        /// in the <see cref="NetworkTransformState"/> returned.
        /// </summary>
        /// <param name="transform">transform to apply</param>
        /// <returns>NetworkTransformState</returns>
        internal NetworkTransformState ApplyLocalNetworkState(Transform transform)
        {
            // Since we never commit these changes, we need to simulate that any changes were committed previously and the bitset
            // value would already be reset prior to having the state applied
            m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick();

            // Now check the transform for any threshold value changes
            ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transform);

            // Return the entire state to be used by the integration test
            return m_LocalAuthoritativeNetworkState;
        }

        /// <summary>
        /// Used for integration testing
        /// </summary>
        internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
        {
            m_CachedNetworkManager = NetworkManager;
            // Apply the interpolate and PostionDeltaCompression flags, otherwise we get false positives whether something changed or not.
            networkState.UseInterpolation = Interpolate;
            networkState.QuaternionSync = UseQuaternionSynchronization;
            networkState.UseHalfFloatPrecision = UseHalfFloatPrecision;
            networkState.QuaternionCompression = UseQuaternionCompression;
            networkState.UseUnreliableDeltas = UseUnreliableDeltas;
            m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));

            return ApplyTransformToNetworkStateWithInfo(ref networkState, ref transformToUse);
        }

        /// <summary>
        /// Applies the transform to the <see cref="NetworkTransformState"/> specified.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, ref Transform transformToUse, bool isSynchronization = false, ulong targetClientId = 0)
        {
            // As long as we are not doing our first synchronization and we are sending unreliable deltas, each
            // NetworkTransform will stagger its full transfom synchronization over a 1 second period based on the
            // assigned tick slot (m_TickSync).
            // More about m_DeltaSynch:
            // If we have not sent any deltas since our last frame synch, then this will prevent us from sending
            // frame synch's when the object is at rest. If this is false and a state update is detected and sent,
            // then it will be set to true and each subsequent tick will do this check to determine if it should
            // send a full frame synch.
            var isAxisSync = false;
            // We compare against the NetworkTickSystem version since ServerTime is set when updating ticks
            if (UseUnreliableDeltas && !isSynchronization && m_DeltaSynch && m_NextTickSync <= m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick)
            {
                // Increment to the next frame synch tick position for this instance
                m_NextTickSync += (int)m_CachedNetworkManager.NetworkConfig.TickRate;
                // If we are teleporting, we do not need to send a frame synch for this tick slot
                // as a "frame synch" really is effectively just a teleport.
                isAxisSync = !networkState.IsTeleportingNextFrame;
                // Reset our delta synch trigger so we don't send another frame synch until we
                // send at least 1 unreliable state update after this fame synch or teleport
                m_DeltaSynch = false;
            }
            // This is used to determine if we need to send the state update reliably (if we are doing an axial sync)
            networkState.UnreliableFrameSync = isAxisSync;

            var isTeleportingAndNotSynchronizing = networkState.IsTeleportingNextFrame && !isSynchronization;
            var isDirty = false;
            var isPositionDirty = isTeleportingAndNotSynchronizing ? networkState.HasPositionChange : false;
            var isRotationDirty = isTeleportingAndNotSynchronizing ? networkState.HasRotAngleChange : false;
            var isScaleDirty = isTeleportingAndNotSynchronizing ? networkState.HasScaleChange : false;

            var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position;
            var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles;
            var scale = transformToUse.localScale;
            networkState.IsSynchronizing = isSynchronization;

            // Check for parenting when synchronizing and/or teleporting
            if (isSynchronization || networkState.IsTeleportingNextFrame)
            {
                // This all has to do with complex nested hierarchies and how it impacts scale
                // when set for the first time or teleporting and depends upon whether the
                // NetworkObject is parented (or "de-parented") at the same time any scale
                // values are applied.
                var hasParentNetworkObject = false;

                // If the NetworkObject belonging to this NetworkTransform instance has a parent
                // (i.e. this handles nested NetworkTransforms under a parent at some layer above)
                if (NetworkObject.transform.parent != null)
                {
                    var parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();

                    // In-scene placed NetworkObjects parented under a GameObject with no
                    // NetworkObject preserve their lossyScale when synchronizing.
                    if (parentNetworkObject == null && NetworkObject.IsSceneObject != false)
                    {
                        hasParentNetworkObject = true;
                    }
                    else
                    {
                        // Or if the relative NetworkObject has a parent NetworkObject
                        hasParentNetworkObject = parentNetworkObject != null;
                    }
                }

                networkState.IsParented = hasParentNetworkObject;

                // When synchronizing with a parent, world position stays impacts position whether
                // the NetworkTransform is using world or local space synchronization.
                // WorldPositionStays: (always use world space)
                // !WorldPositionStays: (always use local space)
                if (isSynchronization)
                {
                    if (NetworkObject.WorldPositionStays())
                    {
                        position = transformToUse.position;
                    }
                    else
                    {
                        position = transformToUse.localPosition;
                    }
                }
            }

            // All of the checks below, up to the delta position checking portion, are to determine if the
            // authority changed a property during runtime that requires a full synchronizing.
            if (InLocalSpace != networkState.InLocalSpace)
            {
                networkState.InLocalSpace = InLocalSpace;
                isDirty = true;
                networkState.IsTeleportingNextFrame = true;
            }

            if (Interpolate != networkState.UseInterpolation)
            {
                networkState.UseInterpolation = Interpolate;
                isDirty = true;
                // When we change from interpolating to not interpolating (or vice versa) we need to synchronize/reset everything
                networkState.IsTeleportingNextFrame = true;
            }

            if (UseQuaternionSynchronization != networkState.QuaternionSync)
            {
                networkState.QuaternionSync = UseQuaternionSynchronization;
                isDirty = true;
                networkState.IsTeleportingNextFrame = true;
            }

            if (UseQuaternionCompression != networkState.QuaternionCompression)
            {
                networkState.QuaternionCompression = UseQuaternionCompression;
                isDirty = true;
                networkState.IsTeleportingNextFrame = true;
            }

            if (UseHalfFloatPrecision != networkState.UseHalfFloatPrecision)
            {
                networkState.UseHalfFloatPrecision = UseHalfFloatPrecision;
                isDirty = true;
                networkState.IsTeleportingNextFrame = true;
            }

            if (SlerpPosition != networkState.UsePositionSlerp)
            {
                networkState.UsePositionSlerp = SlerpPosition;
                isDirty = true;
                networkState.IsTeleportingNextFrame = true;
            }

            if (UseUnreliableDeltas != networkState.UseUnreliableDeltas)
            {
                networkState.UseUnreliableDeltas = UseUnreliableDeltas;
                isDirty = true;
                networkState.IsTeleportingNextFrame = true;
            }

            // Begin delta checks against last sent state update
            if (!UseHalfFloatPrecision)
            {
                if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                {
                    networkState.PositionX = position.x;
                    networkState.HasPositionX = true;
                    isPositionDirty = true;
                }

                if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                {
                    networkState.PositionY = position.y;
                    networkState.HasPositionY = true;
                    isPositionDirty = true;
                }

                if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                {
                    networkState.PositionZ = position.z;
                    networkState.HasPositionZ = true;
                    isPositionDirty = true;
                }
            }
            else if (SynchronizePosition)
            {
                // If we are teleporting then we can skip the delta threshold check
                isPositionDirty = networkState.IsTeleportingNextFrame || isAxisSync;
                if (m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick)
                {
                    isPositionDirty = true;
                }

                // For NetworkDeltaPosition, if any axial value is dirty then we always send a full update
                if (!isPositionDirty)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        if (Math.Abs(position[i] - m_HalfPositionState.PreviousPosition[i]) >= PositionThreshold)
                        {
                            isPositionDirty = i == 0 ? SyncPositionX : i == 1 ? SyncPositionY : SyncPositionZ;
                            if (!isPositionDirty)
                            {
                                continue;
                            }
                            break;
                        }
                    }
                }

                // If the position is dirty or we are teleporting (which includes synchronization)
                // then determine what parts of the NetworkDeltaPosition should be updated
                if (isPositionDirty)
                {
                    // If we are not synchronizing the transform state for the first time
                    if (!isSynchronization)
                    {
                        // With global teleporting (broadcast to all non-authority instances)
                        // we re-initialize authority's NetworkDeltaPosition and synchronize all
                        // non-authority instances with the new full precision position
                        if (networkState.IsTeleportingNextFrame)
                        {
                            m_HalfPositionState = new NetworkDeltaPosition(position, networkState.NetworkTick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                            networkState.CurrentPosition = position;
                        }
                        else // Otherwise, just synchronize the delta position value
                        {
                            m_HalfPositionState.HalfVector3.AxisToSynchronize = math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ);
                            m_HalfPositionState.UpdateFrom(ref position, networkState.NetworkTick);
                        }

                        networkState.NetworkDeltaPosition = m_HalfPositionState;

                        // If ownership offset is greater or we are doing an axial synchronization then synchronize the base position 
                        if ((m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick || isAxisSync) && !networkState.IsTeleportingNextFrame)
                        {
                            networkState.SynchronizeBaseHalfFloat = true;
                        }
                        else
                        {
                            networkState.SynchronizeBaseHalfFloat = UseUnreliableDeltas ? m_HalfPositionState.CollapsedDeltaIntoBase : false;
                        }
                    }
                    else // If synchronizing is set, then use the current full position value on the server side
                    {
                        if (ShouldSynchronizeHalfFloat(targetClientId))
                        {
                            // If we have a NetworkDeltaPosition that has a state applied, then we want to determine
                            // what needs to be synchronized. For owner authoritative mode, the server side
                            // will have no valid state yet.
                            if (m_HalfPositionState.NetworkTick > 0)
                            {
                                // Always synchronize the base position and the ushort values of the
                                // current m_HalfPositionState
                                networkState.CurrentPosition = m_HalfPositionState.CurrentBasePosition;
                                networkState.NetworkDeltaPosition = m_HalfPositionState;
                                // If the server is the owner, in both server and owner authoritative modes,
                                // or we are running in server authoritative mode, then we use the
                                // HalfDeltaConvertedBack value as the delta position
                                if (NetworkObject.IsOwnedByServer || IsServerAuthoritative())
                                {
                                    networkState.DeltaPosition = m_HalfPositionState.HalfDeltaConvertedBack;
                                }
                                else
                                {
                                    // Otherwise, we are in owner authoritative mode and the server's NetworkDeltaPosition
                                    // state is "non-authoritative" relative so we use the DeltaPosition.
                                    networkState.DeltaPosition = m_HalfPositionState.DeltaPosition;
                                }
                            }
                            else // Reset everything and just send the current position
                            {
                                networkState.NetworkDeltaPosition = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                                networkState.DeltaPosition = Vector3.zero;
                                networkState.CurrentPosition = position;
                            }
                        }
                        else
                        {
                            networkState.NetworkDeltaPosition = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                            networkState.CurrentPosition = position;
                        }
                        // Add log entry for this update relative to the client being synchronized
                        AddLogEntry(ref networkState, targetClientId, true);
                    }
                    networkState.HasPositionX = SyncPositionX;
                    networkState.HasPositionY = SyncPositionY;
                    networkState.HasPositionZ = SyncPositionZ;
                }
            }

            if (!UseQuaternionSynchronization)
            {
                if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                {
                    networkState.RotAngleX = rotAngles.x;
                    networkState.HasRotAngleX = true;
                    isRotationDirty = true;
                }

                if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                {
                    networkState.RotAngleY = rotAngles.y;
                    networkState.HasRotAngleY = true;
                    isRotationDirty = true;
                }

                if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                {
                    networkState.RotAngleZ = rotAngles.z;
                    networkState.HasRotAngleZ = true;
                    isRotationDirty = true;
                }
            }
            else if (SynchronizeRotation)
            {
                // If we are teleporting then we can skip the delta threshold check
                isRotationDirty = networkState.IsTeleportingNextFrame || isAxisSync;
                // For quaternion synchronization, if one angle is dirty we send a full update
                if (!isRotationDirty)
                {
                    var previousRotation = networkState.Rotation.eulerAngles;
                    for (int i = 0; i < 3; i++)
                    {
                        if (Mathf.Abs(Mathf.DeltaAngle(previousRotation[i], rotAngles[i])) >= RotAngleThreshold)
                        {
                            isRotationDirty = true;
                            break;
                        }
                    }
                }
                if (isRotationDirty)
                {
                    networkState.Rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation;
                    networkState.HasRotAngleX = true;
                    networkState.HasRotAngleY = true;
                    networkState.HasRotAngleZ = true;
                }
            }

            // For scale, we need to check for parenting when synchronizing and/or teleporting
            if (isSynchronization || networkState.IsTeleportingNextFrame)
            {
                // If we are synchronizing and the associated NetworkObject has a parent then we want to send the
                // LossyScale if the NetworkObject has a parent since NetworkObject spawn order is not guaranteed
                if (networkState.IsParented)
                {
                    networkState.LossyScale = transform.lossyScale;
                }
            }

            // Checking scale deltas when not synchronizing
            if (!isSynchronization)
            {
                if (!UseHalfFloatPrecision)
                {
                    if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                    {
                        networkState.ScaleX = scale.x;
                        networkState.HasScaleX = true;
                        isScaleDirty = true;
                    }

                    if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                    {
                        networkState.ScaleY = scale.y;
                        networkState.HasScaleY = true;
                        isScaleDirty = true;
                    }

                    if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
                    {
                        networkState.ScaleZ = scale.z;
                        networkState.HasScaleZ = true;
                        isScaleDirty = true;
                    }
                }
                else if (SynchronizeScale)
                {
                    var previousScale = networkState.Scale;
                    for (int i = 0; i < 3; i++)
                    {
                        if (Mathf.Abs(scale[i] - previousScale[i]) >= ScaleThreshold || networkState.IsTeleportingNextFrame || isAxisSync)
                        {
                            isScaleDirty = true;
                            networkState.Scale[i] = scale[i];
                            networkState.SetHasScale(i, i == 0 ? SyncScaleX : i == 1 ? SyncScaleY : SyncScaleZ);
                        }
                    }
                }
            }
            else // Just apply the full local scale when synchronizing
            if (SynchronizeScale)
            {
                if (!UseHalfFloatPrecision)
                {
                    networkState.ScaleX = transform.localScale.x;
                    networkState.ScaleY = transform.localScale.y;
                    networkState.ScaleZ = transform.localScale.z;
                }
                else
                {
                    networkState.Scale = transform.localScale;
                }
                networkState.HasScaleX = true;
                networkState.HasScaleY = true;
                networkState.HasScaleZ = true;
                isScaleDirty = true;
            }
            isDirty |= isPositionDirty || isRotationDirty || isScaleDirty;

            if (isDirty)
            {
                // Some integration/unit tests disable the NetworkTransform and there is no
                // NetworkManager
                if (enabled)
                {
                    // We use the NetworkTickSystem version since ServerTime is set when updating ticks
                    networkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick;
                }
            }

            // Mark the state dirty for the next network tick update to clear out the bitset values
            networkState.IsDirty |= isDirty;
            return isDirty;
        }

        /// <summary>
        /// Applies the authoritative state to the transform
        /// </summary>
        private void ApplyAuthoritativeState()
        {
            var networkState = m_LocalAuthoritativeNetworkState;
            // The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated
            // at the end of this method and assure that when not interpolating the non-authoritative side
            // cannot make adjustments to any portions the transform not being synchronized.
            var adjustedPosition = m_CurrentPosition;
            var adjustedRotation = m_CurrentRotation;
            var adjustedRotAngles = adjustedRotation.eulerAngles;
            var adjustedScale = m_CurrentScale;

            // Non-Authority Preservers the authority's transform state update modes
            InLocalSpace = networkState.InLocalSpace;
            Interpolate = networkState.UseInterpolation;
            UseHalfFloatPrecision = networkState.UseHalfFloatPrecision;
            UseQuaternionSynchronization = networkState.QuaternionSync;
            UseQuaternionCompression = networkState.QuaternionCompression;
            UseUnreliableDeltas = networkState.UseUnreliableDeltas;

            if (SlerpPosition != networkState.UsePositionSlerp)
            {
                SlerpPosition = networkState.UsePositionSlerp;
                UpdatePositionSlerp();
            }

            // NOTE ABOUT INTERPOLATING AND THE CODE BELOW:
            // We always apply the interpolated state for any axis we are synchronizing even when the state has no deltas
            // to assure we fully interpolate to our target even after we stop extrapolating 1 tick later.
            if (Interpolate)
            {
                if (SynchronizePosition)
                {
                    var interpolatedPosition = m_PositionInterpolator.GetInterpolatedValue();
                    if (UseHalfFloatPrecision)
                    {
                        adjustedPosition = interpolatedPosition;
                    }
                    else
                    {
                        if (SyncPositionX) { adjustedPosition.x = interpolatedPosition.x; }
                        if (SyncPositionY) { adjustedPosition.y = interpolatedPosition.y; }
                        if (SyncPositionZ) { adjustedPosition.z = interpolatedPosition.z; }
                    }
                }

                if (SynchronizeScale)
                {
                    if (UseHalfFloatPrecision)
                    {
                        adjustedScale = m_ScaleInterpolator.GetInterpolatedValue();
                    }
                    else
                    {
                        var interpolatedScale = m_ScaleInterpolator.GetInterpolatedValue();
                        if (SyncScaleX) { adjustedScale.x = interpolatedScale.x; }
                        if (SyncScaleY) { adjustedScale.y = interpolatedScale.y; }
                        if (SyncScaleZ) { adjustedScale.z = interpolatedScale.z; }
                    }
                }

                if (SynchronizeRotation)
                {
                    var interpolatedRotation = m_RotationInterpolator.GetInterpolatedValue();
                    if (UseQuaternionSynchronization)
                    {
                        adjustedRotation = interpolatedRotation;
                    }
                    else
                    {
                        var interpolatedEulerAngles = interpolatedRotation.eulerAngles;
                        if (SyncRotAngleX) { adjustedRotAngles.x = interpolatedEulerAngles.x; }
                        if (SyncRotAngleY) { adjustedRotAngles.y = interpolatedEulerAngles.y; }
                        if (SyncRotAngleZ) { adjustedRotAngles.z = interpolatedEulerAngles.z; }
                        adjustedRotation.eulerAngles = adjustedRotAngles;
                    }
                }
            }
            else
            {
                // Non-Interpolated Position and Scale
                if (UseHalfFloatPrecision)
                {
                    if (networkState.HasPositionChange && SynchronizePosition)
                    {
                        adjustedPosition = m_TargetPosition;
                    }

                    if (networkState.HasScaleChange && SynchronizeScale)
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            if (m_LocalAuthoritativeNetworkState.HasScale(i))
                            {
                                adjustedScale[i] = m_LocalAuthoritativeNetworkState.Scale[i];
                            }
                        }
                    }
                }
                else
                {
                    if (networkState.HasPositionX) { adjustedPosition.x = networkState.PositionX; }
                    if (networkState.HasPositionY) { adjustedPosition.y = networkState.PositionY; }
                    if (networkState.HasPositionZ) { adjustedPosition.z = networkState.PositionZ; }
                    if (networkState.HasScaleX) { adjustedScale.x = networkState.ScaleX; }
                    if (networkState.HasScaleY) { adjustedScale.y = networkState.ScaleY; }
                    if (networkState.HasScaleZ) { adjustedScale.z = networkState.ScaleZ; }
                }

                // Non-interpolated rotation
                if (SynchronizeRotation)
                {
                    if (networkState.QuaternionSync && networkState.HasRotAngleChange)
                    {
                        adjustedRotation = networkState.Rotation;
                    }
                    else
                    {
                        if (networkState.HasRotAngleX) { adjustedRotAngles.x = networkState.RotAngleX; }
                        if (networkState.HasRotAngleY) { adjustedRotAngles.y = networkState.RotAngleY; }
                        if (networkState.HasRotAngleZ) { adjustedRotAngles.z = networkState.RotAngleZ; }
                        adjustedRotation.eulerAngles = adjustedRotAngles;
                    }
                }
            }

            // Apply the position if we are synchronizing position
            if (SynchronizePosition)
            {
                // Update our current position if it changed or we are interpolating
                if (networkState.HasPositionChange || Interpolate)
                {
                    m_CurrentPosition = adjustedPosition;
                }
                if (InLocalSpace)
                {
                    transform.localPosition = m_CurrentPosition;
                }
                else
                {
                    transform.position = m_CurrentPosition;
                }
            }

            // Apply the rotation if we are synchronizing rotation
            if (SynchronizeRotation)
            {
                // Update our current rotation if it changed or we are interpolating
                if (networkState.HasRotAngleChange || Interpolate)
                {
                    m_CurrentRotation = adjustedRotation;
                }
                if (InLocalSpace)
                {
                    transform.localRotation = m_CurrentRotation;
                }
                else
                {
                    transform.rotation = m_CurrentRotation;
                }
            }

            // Apply the scale if we are synchronizing scale
            if (SynchronizeScale)
            {
                // Update our current scale if it changed or we are interpolating
                if (networkState.HasScaleChange || Interpolate)
                {
                    m_CurrentScale = adjustedScale;
                }
                transform.localScale = m_CurrentScale;
            }
        }

        /// <summary>
        /// Handles applying the full authoritative state (i.e. teleporting)
        /// </summary>
        /// <remarks>
        /// Only non-authoritative instances should invoke this
        /// </remarks>
        private void ApplyTeleportingState(NetworkTransformState newState)
        {
            if (!newState.IsTeleportingNextFrame)
            {
                return;
            }

            var sentTime = newState.SentTime;
            var currentPosition = GetSpaceRelativePosition();
            var currentRotation = GetSpaceRelativeRotation();
            var currentEulerAngles = currentRotation.eulerAngles;
            var currentScale = transform.localScale;

            var isSynchronization = newState.IsSynchronizing;

            // Clear all interpolators
            m_ScaleInterpolator.Clear();
            m_PositionInterpolator.Clear();
            m_RotationInterpolator.Clear();

            if (newState.HasPositionChange)
            {
                if (!UseHalfFloatPrecision)
                {
                    // Adjust based on which axis changed
                    if (newState.HasPositionX)
                    {
                        currentPosition.x = newState.PositionX;
                    }

                    if (newState.HasPositionY)
                    {
                        currentPosition.y = newState.PositionY;
                    }

                    if (newState.HasPositionZ)
                    {
                        currentPosition.z = newState.PositionZ;
                    }
                }
                else
                {
                    // With delta position teleport updates or synchronization, we create a new instance and provide the current network tick.
                    m_HalfPositionState = new NetworkDeltaPosition(newState.CurrentPosition, newState.NetworkTick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));

                    // When first synchronizing we determine if we need to apply the current delta position
                    // offset or not. This is specific to owner authoritative mode on the owner side only
                    if (isSynchronization)
                    {
                        // Need to use NetworkManager vs m_CachedNetworkManager here since we are yet to be spawned
                        if (ShouldSynchronizeHalfFloat(NetworkManager.LocalClientId))
                        {
                            m_HalfPositionState.HalfVector3.Axis = newState.NetworkDeltaPosition.HalfVector3.Axis;
                            m_HalfPositionState.DeltaPosition = newState.DeltaPosition;
                            currentPosition = m_HalfPositionState.ToVector3(newState.NetworkTick);
                        }
                        else
                        {
                            currentPosition = newState.CurrentPosition;
                        }
                        // Before the state is applied add a log entry if AddLogEntry is assigned
                        AddLogEntry(ref newState, NetworkObject.OwnerClientId, true);
                    }
                    else
                    {
                        // If we are just teleporting, then we already created a new NetworkDeltaPosition value.
                        // set the current position to the state's current position
                        currentPosition = newState.CurrentPosition;
                    }
                }

                m_CurrentPosition = currentPosition;
                m_TargetPosition = currentPosition;

                // Apply the position
                if (newState.InLocalSpace)
                {
                    transform.localPosition = currentPosition;
                }
                else
                {
                    transform.position = currentPosition;
                }

                if (Interpolate)
                {
                    UpdatePositionInterpolator(currentPosition, sentTime, true);
                }
            }

            if (newState.HasScaleChange)
            {
                bool shouldUseLossy = false;
                if (newState.IsParented)
                {
                    if (transform.parent == null)
                    {
                        shouldUseLossy = NetworkObject.WorldPositionStays();
                    }
                    else
                    {
                        shouldUseLossy = !NetworkObject.WorldPositionStays();
                    }
                }

                if (UseHalfFloatPrecision)
                {
                    currentScale = shouldUseLossy ? newState.LossyScale : newState.Scale;
                }
                else
                {
                    // Adjust based on which axis changed
                    if (newState.HasScaleX)
                    {
                        currentScale.x = shouldUseLossy ? newState.LossyScale.x : newState.ScaleX;
                    }

                    if (newState.HasScaleY)
                    {
                        currentScale.y = shouldUseLossy ? newState.LossyScale.y : newState.ScaleY;
                    }

                    if (newState.HasScaleZ)
                    {
                        currentScale.z = shouldUseLossy ? newState.LossyScale.z : newState.ScaleZ;
                    }
                }

                m_CurrentScale = currentScale;
                m_TargetScale = currentScale;

                // Apply the adjusted scale
                transform.localScale = currentScale;

                if (Interpolate)
                {
                    m_ScaleInterpolator.ResetTo(currentScale, sentTime);
                }
            }

            if (newState.HasRotAngleChange)
            {
                if (newState.QuaternionSync)
                {
                    currentRotation = newState.Rotation;
                }
                else
                {
                    // Adjust based on which axis changed
                    if (newState.HasRotAngleX)
                    {
                        currentEulerAngles.x = newState.RotAngleX;
                    }

                    if (newState.HasRotAngleY)
                    {
                        currentEulerAngles.y = newState.RotAngleY;
                    }

                    if (newState.HasRotAngleZ)
                    {
                        currentEulerAngles.z = newState.RotAngleZ;
                    }
                    currentRotation.eulerAngles = currentEulerAngles;
                }

                m_CurrentRotation = currentRotation;
                m_TargetRotation = currentRotation.eulerAngles;

                if (InLocalSpace)
                {
                    transform.localRotation = currentRotation;
                }
                else
                {
                    transform.rotation = currentRotation;
                }

                if (Interpolate)
                {
                    m_RotationInterpolator.ResetTo(currentRotation, sentTime);
                }
            }

            // Add log after to applying the update if AddLogEntry is defined
            if (isSynchronization)
            {
                AddLogEntry(ref newState, NetworkObject.OwnerClientId);
            }
        }

        /// <summary>
        /// Adds the new state's values to their respective interpolator
        /// </summary>
        /// <remarks>
        /// Only non-authoritative instances should invoke this
        /// </remarks>
        private void ApplyUpdatedState(NetworkTransformState newState)
        {
            // Set the transforms's synchronization modes
            InLocalSpace = newState.InLocalSpace;
            Interpolate = newState.UseInterpolation;
            UseQuaternionSynchronization = newState.QuaternionSync;
            UseQuaternionCompression = newState.QuaternionCompression;
            UseHalfFloatPrecision = newState.UseHalfFloatPrecision;
            UseUnreliableDeltas = newState.UseUnreliableDeltas;

            if (SlerpPosition != newState.UsePositionSlerp)
            {
                SlerpPosition = newState.UsePositionSlerp;
                UpdatePositionSlerp();
            }

            m_LocalAuthoritativeNetworkState = newState;
            if (m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame)
            {
                ApplyTeleportingState(m_LocalAuthoritativeNetworkState);
                return;
            }

            var sentTime = newState.SentTime;
            var currentRotation = GetSpaceRelativeRotation();
            var currentEulerAngles = currentRotation.eulerAngles;

            // Only if using half float precision and our position had changed last update then
            if (UseHalfFloatPrecision && m_LocalAuthoritativeNetworkState.HasPositionChange)
            {
                if (m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat)
                {
                    m_HalfPositionState = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition;
                }
                else
                {
                    // assure our local NetworkDeltaPosition state is updated
                    m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis;
                    m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition;

                    // This is to assure when you get the position of the state it is the correct position
                    m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.ToVector3(0);
                }
                // Update our target position
                m_TargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick);
                m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition;
            }

            if (!Interpolate)
            {
                return;
            }

            // Apply axial changes from the new state
            // Either apply the delta position target position or the current state's delta position
            // depending upon whether UsePositionDeltaCompression is enabled
            if (m_LocalAuthoritativeNetworkState.HasPositionChange)
            {
                if (!m_LocalAuthoritativeNetworkState.UseHalfFloatPrecision)
                {
                    var newTargetPosition = m_TargetPosition;
                    if (m_LocalAuthoritativeNetworkState.HasPositionX)
                    {
                        newTargetPosition.x = m_LocalAuthoritativeNetworkState.PositionX;
                    }

                    if (m_LocalAuthoritativeNetworkState.HasPositionY)
                    {
                        newTargetPosition.y = m_LocalAuthoritativeNetworkState.PositionY;
                    }

                    if (m_LocalAuthoritativeNetworkState.HasPositionZ)
                    {
                        newTargetPosition.z = m_LocalAuthoritativeNetworkState.PositionZ;
                    }
                    m_TargetPosition = newTargetPosition;
                }
                UpdatePositionInterpolator(m_TargetPosition, sentTime);
            }

            if (m_LocalAuthoritativeNetworkState.HasScaleChange)
            {
                var currentScale = m_TargetScale;
                if (UseHalfFloatPrecision)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        if (m_LocalAuthoritativeNetworkState.HasScale(i))
                        {
                            currentScale[i] = m_LocalAuthoritativeNetworkState.Scale[i];
                        }
                    }
                }
                else
                {
                    if (m_LocalAuthoritativeNetworkState.HasScaleX)
                    {
                        currentScale.x = m_LocalAuthoritativeNetworkState.ScaleX;
                    }

                    if (m_LocalAuthoritativeNetworkState.HasScaleY)
                    {
                        currentScale.y = m_LocalAuthoritativeNetworkState.ScaleY;
                    }

                    if (m_LocalAuthoritativeNetworkState.HasScaleZ)
                    {
                        currentScale.z = m_LocalAuthoritativeNetworkState.ScaleZ;
                    }
                }
                m_TargetScale = currentScale;
                m_ScaleInterpolator.AddMeasurement(currentScale, sentTime);
            }

            // With rotation, we check if there are any changes first and
            // if so then apply the changes to the current Euler rotation
            // values.
            if (m_LocalAuthoritativeNetworkState.HasRotAngleChange)
            {
                if (m_LocalAuthoritativeNetworkState.QuaternionSync)
                {
                    currentRotation = m_LocalAuthoritativeNetworkState.Rotation;
                }
                else
                {
                    currentEulerAngles = m_TargetRotation;
                    // Adjust based on which axis changed
                    // (both half precision and full precision apply Eulers to the RotAngle properties when reading the update)
                    if (m_LocalAuthoritativeNetworkState.HasRotAngleX)
                    {
                        currentEulerAngles.x = m_LocalAuthoritativeNetworkState.RotAngleX;
                    }

                    if (m_LocalAuthoritativeNetworkState.HasRotAngleY)
                    {
                        currentEulerAngles.y = m_LocalAuthoritativeNetworkState.RotAngleY;
                    }

                    if (m_LocalAuthoritativeNetworkState.HasRotAngleZ)
                    {
                        currentEulerAngles.z = m_LocalAuthoritativeNetworkState.RotAngleZ;
                    }
                    m_TargetRotation = currentEulerAngles;
                    currentRotation.eulerAngles = currentEulerAngles;
                }

                m_RotationInterpolator.AddMeasurement(currentRotation, sentTime);
            }
        }

        /// <summary>
        /// Invoked on the non-authoritative side when the NetworkTransformState has been updated
        /// </summary>
        /// <param name="oldState">the previous <see cref="NetworkTransformState"/></param>
        /// <param name="newState">the new <see cref="NetworkTransformState"/></param>
        protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState)
        {

        }

        private NetworkTransformState m_OldState = new NetworkTransformState();

        /// <summary>
        /// Only non-authoritative instances should invoke this method
        /// </summary>
        private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransformState newState)
        {
            if (!NetworkObject.IsSpawned || CanCommitToTransform)
            {
                return;
            }

            // If we are using UseUnreliableDeltas and our old state tick is greater than the new state tick,
            // then just ignore the newstate. This avoids any scenario where the new state is out of order
            // from the old state (with unreliable traffic and/or mixed unreliable and reliable)
            if (UseUnreliableDeltas && oldState.NetworkTick > newState.NetworkTick && !newState.IsTeleportingNextFrame && !newState.UnreliableFrameSync)
            {
                return;
            }

            // Get the time when this new state was sent
            newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time;

            // Apply the new state
            ApplyUpdatedState(newState);

            // Provide notifications when the state has been updated
            // We use the m_LocalAuthoritativeNetworkState because newState has been applied and adjustments could have
            // been made (i.e. half float precision position values will have been updated)
            OnNetworkTransformStateUpdated(ref oldState, ref m_LocalAuthoritativeNetworkState);
        }

        /// <summary>
        /// Will set the maximum interpolation boundary for the interpolators of this <see cref="NetworkTransform"/> instance.
        /// This value roughly translates to the maximum value of 't' in <see cref="Vector3.Lerp(Vector3, Vector3, float)"/> and
        /// <see cref="Quaternion.Lerp(Quaternion, Quaternion, float)"/> for all transform elements being monitored by
        /// <see cref="NetworkTransform"/> (i.e. Position, Scale, and Rotation)
        /// </summary>
        /// <param name="maxInterpolationBound">Maximum time boundary that can be used in a frame when interpolating between two values</param>
        public void SetMaxInterpolationBound(float maxInterpolationBound)
        {
            m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound;
            m_PositionInterpolator.MaxInterpolationBound = maxInterpolationBound;
            m_ScaleInterpolator.MaxInterpolationBound = maxInterpolationBound;
        }

        /// <summary>
        /// Create interpolators when first instantiated to avoid memory allocations if the
        /// associated NetworkObject persists (i.e. despawned but not destroyed or pools)
        /// </summary>
        protected virtual void Awake()
        {
            // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value
            m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion();
            m_PositionInterpolator = new BufferedLinearInterpolatorVector3();
            m_ScaleInterpolator = new BufferedLinearInterpolatorVector3();
        }

        /// <summary>
        /// Checks for changes in the axis to synchronize. If one or more did change it
        /// then determines if the axis were enabled and if the delta between the last known
        /// delta position and the current position for the axis exceeds the adjustment range
        /// before it is collapsed into the base position.
        /// If it does exceed the adjustment range, then we have to teleport the object so
        /// a full position synchronization takes place and the NetworkDeltaPosition is
        /// reset with the updated base position that it then will generating a new delta position from.
        /// </summary>
        /// <remarks>
        /// This only happens if a user disables an axis, continues to update the disabled axis,
        /// and then later enables the axis. (which will not be a recommended best practice)
        /// </remarks>
        private void AxisChangedDeltaPositionCheck()
        {
            if (UseHalfFloatPrecision && SynchronizePosition)
            {
                var synAxis = m_HalfPositionState.HalfVector3.AxisToSynchronize;
                if (SyncPositionX != synAxis.x || SyncPositionY != synAxis.y || SyncPositionZ != synAxis.z)
                {
                    var positionState = m_HalfPositionState.GetFullPosition();
                    var relativePosition = GetSpaceRelativePosition();
                    bool needsToTeleport = false;
                    // Only if the synchronization of an axis is turned on do we need to
                    // check if a teleport is required due to the delta from the last known
                    // to the currently known axis value exceeds MaxDeltaBeforeAdjustment.
                    if (SyncPositionX && SyncPositionX != synAxis.x)
                    {
                        needsToTeleport = Mathf.Abs(relativePosition.x - positionState.x) >= NetworkDeltaPosition.MaxDeltaBeforeAdjustment;
                    }
                    if (SyncPositionY && SyncPositionY != synAxis.y)
                    {
                        needsToTeleport = Mathf.Abs(relativePosition.y - positionState.y) >= NetworkDeltaPosition.MaxDeltaBeforeAdjustment;
                    }
                    if (SyncPositionZ && SyncPositionZ != synAxis.z)
                    {
                        needsToTeleport = Mathf.Abs(relativePosition.z - positionState.z) >= NetworkDeltaPosition.MaxDeltaBeforeAdjustment;
                    }
                    // If needed, force a teleport as the delta is outside of the valid delta boundary
                    m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = needsToTeleport;
                }
            }
        }

        /// <summary>
        /// Called by authority to check for deltas and update non-authoritative instances
        /// if any are found.
        /// </summary>
        internal void OnUpdateAuthoritativeState(ref Transform transformSource)
        {
            // If our replicated state is not dirty and our local authority state is dirty, clear it.
            if (!m_LocalAuthoritativeNetworkState.ExplicitSet && m_LocalAuthoritativeNetworkState.IsDirty && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame)
            {
                // Now clear our bitset and prepare for next network tick state update
                m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick();
                if (TrackByStateId)
                {
                    m_LocalAuthoritativeNetworkState.TrackByStateId = true;
                    m_LocalAuthoritativeNetworkState.StateId++;
                }
                else
                {
                    m_LocalAuthoritativeNetworkState.TrackByStateId = false;
                }
            }

            AxisChangedDeltaPositionCheck();

            TryCommitTransform(ref transformSource);
        }

        /// <summary>
        /// Authority subscribes to network tick events and will invoke
        /// <see cref="OnUpdateAuthoritativeState(ref Transform)"/> each network tick.
        /// </summary>
        private void NetworkTickSystem_Tick()
        {
            // As long as we are still authority
            if (CanCommitToTransform)
            {
                // Update any changes to the transform
                var transformSource = transform;
                OnUpdateAuthoritativeState(ref transformSource);

                m_CurrentPosition = GetSpaceRelativePosition();
                m_TargetPosition = GetSpaceRelativePosition();
            }
            else // If we are no longer authority, unsubscribe to the tick event
            if (NetworkManager != null && NetworkManager.NetworkTickSystem != null)
            {
                NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
            }
        }

        /// <inheritdoc/>
        public override void OnNetworkSpawn()
        {
            ///////////////////////////////////////////////////////////////
            // NOTE: Legacy and no longer used (candidates for deprecation)
            m_CachedIsServer = IsServer;
            ///////////////////////////////////////////////////////////////

            // Started using this again to avoid the getter processing cost of NetworkBehaviour.NetworkManager
            m_CachedNetworkManager = NetworkManager;

            Initialize();
        }

        /// <inheritdoc/>
        public override void OnNetworkDespawn()
        {
            DeregisterForTickUpdate(this);

            CanCommitToTransform = false;
            if (NetworkManager != null && NetworkManager.NetworkTickSystem != null)
            {
                NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
            }
        }

        /// <inheritdoc/>
        public override void OnDestroy()
        {
            // During destroy, use NetworkBehaviour.NetworkManager as opposed to m_CachedNetworkManager
            if (NetworkManager != null && NetworkManager.NetworkTickSystem != null)
            {
                NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick;
            }
            CanCommitToTransform = false;
            base.OnDestroy();
        }

        /// <inheritdoc/>
        public override void OnLostOwnership()
        {
            base.OnLostOwnership();
        }

        /// <inheritdoc/>
        public override void OnGainedOwnership()
        {
            base.OnGainedOwnership();
        }

        protected override void OnOwnershipChanged(ulong previous, ulong current)
        {
            // If we were the previous owner or the newly assigned owner then reinitialize
            if (current == m_CachedNetworkManager.LocalClientId || previous == m_CachedNetworkManager.LocalClientId)
            {
                InternalInitialization(true);
            }
            base.OnOwnershipChanged(previous, current);
        }

        /// <summary>
        /// Invoked when first spawned and when ownership changes.
        /// </summary>
        /// <param name="replicatedState">the current <see cref="NetworkTransformState"/> after initializing</param>
        protected virtual void OnInitialize(ref NetworkTransformState replicatedState)
        {
        }

        /// <summary>
        /// An owner read and owner write NetworkVariable so it doesn't generate any messages
        /// </summary>
        private NetworkVariable<NetworkTransformState> m_InternalStatNetVar = new NetworkVariable<NetworkTransformState>(default, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner);
        /// <summary>
        /// This method is only invoked by the owner
        /// Use: OnInitialize(ref NetworkTransformState replicatedState) to be notified on all instances
        /// </summary>
        /// <param name="replicatedState"></param>
        protected virtual void OnInitialize(ref NetworkVariable<NetworkTransformState> replicatedState)
        {

        }

        private int m_HalfFloatTargetTickOwnership;

        /// <summary>
        /// The internal initialzation method to allow for internal API adjustments
        /// </summary>
        /// <param name="isOwnershipChange"></param>
        private void InternalInitialization(bool isOwnershipChange = false)
        {
            if (!IsSpawned)
            {
                return;
            }

            CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner;
            var currentPosition = GetSpaceRelativePosition();
            var currentRotation = GetSpaceRelativeRotation();

            if (CanCommitToTransform)
            {
                if (UseHalfFloatPrecision)
                {
                    m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
                }
                m_CurrentPosition = currentPosition;
                m_TargetPosition = currentPosition;

                RegisterForTickUpdate(this);

                m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false;
                if (UseHalfFloatPrecision && isOwnershipChange && !IsServerAuthoritative() && Interpolate)
                {
                    m_HalfFloatTargetTickOwnership = m_CachedNetworkManager.ServerTime.Tick;
                }
            }
            else
            {
                // Remove this instance from the tick update
                DeregisterForTickUpdate(this);

                ResetInterpolatedStateToCurrentAuthoritativeState();
                m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false;
                m_CurrentPosition = currentPosition;
                m_TargetPosition = currentPosition;
                m_CurrentScale = transform.localScale;
                m_TargetScale = transform.localScale;
                m_CurrentRotation = currentRotation;
                m_TargetRotation = currentRotation.eulerAngles;

            }
            OnInitialize(ref m_LocalAuthoritativeNetworkState);

            if (IsOwner)
            {
                m_InternalStatNetVar.Value = m_LocalAuthoritativeNetworkState;
                OnInitialize(ref m_InternalStatNetVar);
            }
        }

        /// <summary>
        /// Initializes NetworkTransform when spawned and ownership changes.
        /// </summary>
        protected void Initialize()
        {
            InternalInitialization();
        }

        /// <inheritdoc/>
        /// <remarks>
        /// When a parent changes, non-authoritative instances should:
        /// - Apply the resultant position, rotation, and scale from the parenting action.
        /// - Clear interpolators (even if not enabled on this frame)
        /// - Reset the interpolators to the position, rotation, and scale resultant values.
        /// This prevents interpolation visual anomalies and issues during initial synchronization
        /// </remarks>
        public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
        {
            // Only if we are not authority
            if (!CanCommitToTransform)
            {
                m_TargetPosition = m_CurrentPosition = GetSpaceRelativePosition();
                m_CurrentRotation = GetSpaceRelativeRotation();
                m_TargetRotation = m_CurrentRotation.eulerAngles;
                m_TargetScale = m_CurrentScale = GetScale();

                if (Interpolate)
                {
                    m_ScaleInterpolator.Clear();
                    m_PositionInterpolator.Clear();
                    m_RotationInterpolator.Clear();

                    // Always use NetworkManager here as this can be invoked prior to spawning
                    var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time;
                    UpdatePositionInterpolator(m_CurrentPosition, tempTime, true);
                    m_ScaleInterpolator.ResetTo(m_CurrentScale, tempTime);
                    m_RotationInterpolator.ResetTo(m_CurrentRotation, tempTime);
                }
            }
            base.OnNetworkObjectParentChanged(parentNetworkObject);
        }

        /// <summary>
        /// Directly sets a state on the authoritative transform.
        /// Owner clients can directly set the state on a server authoritative transform
        /// This will override any changes made previously to the transform
        /// This isn't resistant to network jitter. Server side changes due to this method won't be interpolated.
        /// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb
        ///  just the desired one(s)
        /// </summary>
        /// <param name="posIn"></param> new position to move to.  Can be null
        /// <param name="rotIn"></param> new rotation to rotate to.  Can be null
        /// <param name="scaleIn">new scale to scale to. Can be null</param>
        /// <param name="teleportDisabled">When true (the default) the <see cref="NetworkObject"/> will not be teleported and, if enabled, will interpolate. When false the <see cref="NetworkObject"/> will teleport/apply the parameters provided immediately.</param>
        /// <exception cref="Exception"></exception>
        public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool teleportDisabled = true)
        {
            if (!IsSpawned)
            {
                NetworkLog.LogError($"Cannot commit transform when not spawned!");
                return;
            }

            // Only the server or the owner is allowed to commit a transform
            if (!IsServer && !IsOwner)
            {
                var errorMessage = gameObject != NetworkObject.gameObject ?
                    $"Non-authority instance of {NetworkObject.gameObject.name} is trying to commit a transform on {gameObject.name}!" :
                    $"Non-authority instance of {NetworkObject.gameObject.name} is trying to commit a transform!";
                NetworkLog.LogError(errorMessage);
                return;
            }

            Vector3 pos = posIn == null ? GetSpaceRelativePosition() : posIn.Value;
            Quaternion rot = rotIn == null ? GetSpaceRelativeRotation() : rotIn.Value;
            Vector3 scale = scaleIn == null ? transform.localScale : scaleIn.Value;

            if (!CanCommitToTransform)
            {
                // Preserving the ability for owner authoritative mode to accept state changes from server
                if (IsServer)
                {
                    m_ClientIds[0] = OwnerClientId;
                    m_ClientRpcParams.Send.TargetClientIds = m_ClientIds;
                    SetStateClientRpc(pos, rot, scale, !teleportDisabled, m_ClientRpcParams);
                }
                else // Preserving the ability for server authoritative mode to accept state changes from owner
                {
                    SetStateServerRpc(pos, rot, scale, !teleportDisabled);
                }
                return;
            }

            SetStateInternal(pos, rot, scale, !teleportDisabled);
        }

        /// <summary>
        /// Authoritative only method
        /// Sets the internal state (teleporting or just set state) of the authoritative
        /// transform directly.
        /// </summary>
        private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport)
        {
            if (InLocalSpace)
            {
                transform.localPosition = pos;
                transform.localRotation = rot;
            }
            else
            {
                transform.SetPositionAndRotation(pos, rot);
            }
            transform.localScale = scale;
            m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;

            var transformToCommit = transform;

            // Explicit set states are cumulative during a fractional tick period of time (i.e. each SetState invocation will 
            // update the axial deltas to whatever changes are applied). As such, we need to preserve the dirty and explicit
            // state flags.
            var stateWasDirty = m_LocalAuthoritativeNetworkState.IsDirty;
            var explicitState = m_LocalAuthoritativeNetworkState.ExplicitSet;

            // Apply any delta states to the m_LocalAuthoritativeNetworkState
            var isDirty = ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit);

            // If we were dirty and the explicit state was set (prior to checking for deltas) or the current explicit state is dirty,
            // then we set the explicit state flag.
            m_LocalAuthoritativeNetworkState.ExplicitSet = (stateWasDirty && explicitState) || isDirty;

            // If the current explicit set flag is set, then we are dirty. This assures if more than one explicit set state is invoked
            // in between a fractional tick period and the current explicit set state did not find any deltas that we preserve any
            // previous dirty state.
            m_LocalAuthoritativeNetworkState.IsDirty = m_LocalAuthoritativeNetworkState.ExplicitSet;

        }

        /// <summary>
        /// Invoked by <see cref="SetState"/>, allows a non-owner server to update the transform state
        /// </summary>
        /// <remarks>
        /// Continued support for client-driven server authority model
        /// </remarks>
        [ClientRpc]
        private void SetStateClientRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport, ClientRpcParams clientRpcParams = default)
        {
            // Server dictated state is always applied
            SetStateInternal(pos, rot, scale, shouldTeleport);
        }

        /// <summary>
        /// Invoked by <see cref="SetState"/>, allows an owner-client update the transform state
        /// </summary>
        /// <remarks>
        /// Continued support for client-driven server authority model
        /// </remarks>
        [ServerRpc]
        private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport)
        {
            // server has received this RPC request to move change transform. give the server a chance to modify or even reject the move
            if (OnClientRequestChange != null)
            {
                (pos, rot, scale) = OnClientRequestChange(pos, rot, scale);
            }
            SetStateInternal(pos, rot, scale, shouldTeleport);
        }


        private void UpdateInterpolation()
        {
            // Non-Authority
            if (Interpolate)
            {
                var serverTime = m_CachedNetworkManager.ServerTime;
                var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime;
                var cachedServerTime = serverTime.Time;

                // With owner authoritative mode, non-authority clients can lag behind
                // by more than 1 tick period of time. The current "solution" for now
                // is to make their cachedRenderTime run 2 ticks behind.
                var ticksAgo = !IsServerAuthoritative() && !IsServer ? 2 : 1;
                var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time;

                // Now only update the interpolators for the portions of the transform being synchronized
                if (SynchronizePosition)
                {
                    m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
                }

                if (SynchronizeRotation)
                {
                    // When using half precision Lerp towards the target rotation.
                    // When using full precision Slerp towards the target rotation.
                    /// <see cref="BufferedLinearInterpolatorQuaternion.IsSlerp"/>
                    m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision;
                    m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
                }

                if (SynchronizeScale)
                {
                    m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
                }
            }
        }

        /// <inheritdoc/>
        /// <remarks>
        /// If you override this method, be sure that:
        /// - Non-authority always invokes this base class method.
        /// </remarks>
        protected virtual void Update()
        {
            // If not spawned or this instance has authority, exit early
            if (!IsSpawned || CanCommitToTransform)
            {
                return;
            }

            // Non-Authority
            UpdateInterpolation();

            // Apply the current authoritative state
            ApplyAuthoritativeState();
        }

        /// <summary>
        /// Teleport the transform to the given values without interpolating
        /// </summary>
        /// <param name="newPosition"></param> new position to move to.
        /// <param name="newRotation"></param> new rotation to rotate to.
        /// <param name="newScale">new scale to scale to.</param>
        /// <exception cref="Exception"></exception>
        public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale)
        {
            if (!CanCommitToTransform)
            {
                throw new Exception("Teleporting on non-authoritative side is not allowed!");
            }

            // Teleporting now is as simple as setting the internal state and passing the teleport flag
            SetStateInternal(newPosition, newRotation, newScale, true);
        }

        /// <summary>
        /// Override this method and return false to switch to owner authoritative mode
        /// </summary>
        /// <returns>(<see cref="true"/> or <see cref="false"/>) where when false it runs as owner-client authoritative</returns>
        protected virtual bool OnIsServerAuthoritative()
        {
            return true;
        }

        /// <summary>
        /// Method to determine if this <see cref="NetworkTransform"/> instance is owner or server authoritative.
        /// </summary>
        /// <remarks>
        /// Used by <see cref="NetworkRigidbody"/> to determines if this is server or owner authoritative.
        /// </remarks>
        /// <returns><see cref="true"/> or <see cref="false"/></returns>
        public bool IsServerAuthoritative()
        {
            return OnIsServerAuthoritative();
        }

        /// <summary>
        /// Invoked by <see cref="NetworkTransformMessage"/> to update the transform state
        /// </summary>
        /// <param name="networkTransformState"></param>
        internal void TransformStateUpdate(ref NetworkTransformState networkTransformState)
        {
            // Store the previous/old state
            m_OldState = m_LocalAuthoritativeNetworkState;

            // Assign the new incoming state
            m_LocalAuthoritativeNetworkState = networkTransformState;

            // Apply the state update
            OnNetworkStateChanged(m_OldState, m_LocalAuthoritativeNetworkState);
        }

        /// <summary>
        /// Invoked by the authoritative instance to sends a <see cref="NetworkTransformMessage"/> containing the <see cref="NetworkTransformState"/>
        /// </summary>
        private void UpdateTransformState()
        {
            if (m_CachedNetworkManager.ShutdownInProgress)
            {
                return;
            }

            bool isServerAuthoritative = OnIsServerAuthoritative();
            if (isServerAuthoritative && !IsServer)
            {
                Debug.LogError($"Server authoritative {nameof(NetworkTransform)} can only be updated by the server!");
            }
            else if (!isServerAuthoritative && !IsServer && !IsOwner)
            {
                Debug.LogError($"Owner authoritative {nameof(NetworkTransform)} can only be updated by the owner!");
            }
            var customMessageManager = m_CachedNetworkManager.CustomMessagingManager;

            var networkTransformMessage = new NetworkTransformMessage()
            {
                NetworkObjectId = NetworkObjectId,
                NetworkBehaviourId = NetworkBehaviourId,
                State = m_LocalAuthoritativeNetworkState
            };

            // Determine what network delivery method to use:
            // When to send reliable packets:
            // - If UsUnrealiable is not enabled
            // - If teleporting or synchronizing
            // - If sending an UnrealiableFrameSync or synchronizing the base position of the NetworkDeltaPosition
            var networkDelivery = !UseUnreliableDeltas | m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing
                | m_LocalAuthoritativeNetworkState.UnreliableFrameSync | m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat
                ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced;

            // Server-host always sends updates to all clients (but itself)
            if (IsServer)
            {
                var clientCount = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count;
                for (int i = 0; i < clientCount; i++)
                {
                    var clientId = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId;
                    if (NetworkManager.ServerClientId == clientId)
                    {
                        continue;
                    }
                    if (!NetworkObject.Observers.Contains(clientId))
                    {
                        continue;
                    }
                    NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, clientId);
                }
            }
            else
            {
                // Clients (owner authoritative) send messages to the server-host
                NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, NetworkManager.ServerClientId);
            }
        }

        #region Network Tick Registration and Handling
        private static Dictionary<NetworkManager, NetworkTransformTickRegistration> s_NetworkTickRegistration = new Dictionary<NetworkManager, NetworkTransformTickRegistration>();

        private static void RemoveTickUpdate(NetworkManager networkManager)
        {
            s_NetworkTickRegistration.Remove(networkManager);
        }

        /// <summary>
        /// Having the tick update once and cycling through registered instances to update is evidently less processor
        /// intensive than having each instance subscribe and update individually.
        /// </summary>
        private class NetworkTransformTickRegistration
        {
            private Action m_NetworkTickUpdate;
            private NetworkManager m_NetworkManager;
            public HashSet<NetworkTransform> NetworkTransforms = new HashSet<NetworkTransform>();

            private int m_LastTick;
            private void OnNetworkManagerStopped(bool value)
            {
                Remove();
            }

            public void Remove()
            {
                m_NetworkManager.NetworkTickSystem.Tick -= m_NetworkTickUpdate;
                m_NetworkTickUpdate = null;
                NetworkTransforms.Clear();
                RemoveTickUpdate(m_NetworkManager);
            }

            /// <summary>
            /// Invoked once per network tick, this will update any registered
            /// authority instances.
            /// </summary>
            private void TickUpdate()
            {
                // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before
                if (m_NetworkManager.ServerTime.Tick <= m_LastTick)
                {
                    return;
                }
                foreach (var networkTransform in NetworkTransforms)
                {
                    if (networkTransform.IsSpawned)
                    {
                        networkTransform.NetworkTickSystem_Tick();
                    }
                }
                m_LastTick = m_NetworkManager.ServerTime.Tick;
            }

            public NetworkTransformTickRegistration(NetworkManager networkManager)
            {
                m_NetworkManager = networkManager;
                m_NetworkTickUpdate = new Action(TickUpdate);
                networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate;
                if (networkManager.IsServer)
                {
                    networkManager.OnServerStopped += OnNetworkManagerStopped;
                }
                else
                {
                    networkManager.OnClientStopped += OnNetworkManagerStopped;
                }
            }
        }
        private static int s_TickSynchPosition;
        private int m_NextTickSync;

        internal void RegisterForTickSynchronization()
        {
            s_TickSynchPosition++;
            m_NextTickSync = NetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate);
        }

        /// <summary>
        /// Will register the NetworkTransform instance for the single tick update entry point.
        /// If a NetworkTransformTickRegistration has not yet been registered for the NetworkManager
        /// instance, then create an entry.
        /// </summary>
        /// <param name="networkTransform"></param>
        private static void RegisterForTickUpdate(NetworkTransform networkTransform)
        {
            if (!s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager))
            {
                s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager));
            }
            networkTransform.RegisterForTickSynchronization();
            s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Add(networkTransform);
        }

        /// <summary>
        /// If a NetworkTransformTickRegistration exists for the NetworkManager instance, then this will
        /// remove the NetworkTransform instance from the single tick update entry point. 
        /// </summary>
        /// <param name="networkTransform"></param>
        private static void DeregisterForTickUpdate(NetworkTransform networkTransform)
        {
            if (s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager))
            {
                s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Remove(networkTransform);
                if (s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Count == 0)
                {
                    var registrationEntry = s_NetworkTickRegistration[networkTransform.NetworkManager];
                    registrationEntry.Remove();
                }
            }
        }

        #endregion
    }

    internal interface INetworkTransformLogStateEntry
    {
        void AddLogEntry(NetworkTransform.NetworkTransformState networkTransformState, ulong targetClient, bool preUpdate = false);
    }
}
